From d6601287e88f2ba6d368f27f848843919a96960d Mon Sep 17 00:00:00 2001 From: SujitMBRDI Date: Fri, 21 Jun 2024 11:33:11 +0530 Subject: [PATCH] feat(bpdm-gate): GET endpoint to download csv file template for business partner upload process --- CHANGELOG.md | 1 + .../bpdm/gate/api/GatePartnerUploadApi.kt | 15 ++++++++++ .../gate/api/client/PartnerUploadApiClient.kt | 7 +++++ .../controller/PartnerUploadController.kt | 12 ++++++++ .../bpdm/gate/service/PartnerUploadService.kt | 25 ++++++++++++++++ .../tractusx/bpdm/gate/auth/AuthAdminIT.kt | 3 +- .../bpdm/gate/auth/AuthInputConsumerIT.kt | 3 +- .../bpdm/gate/auth/AuthInputManagerIT.kt | 3 +- .../bpdm/gate/auth/AuthOutputConsumerIT.kt | 3 +- .../tractusx/bpdm/gate/auth/AuthTestBase.kt | 8 ++++- .../tractusx/bpdm/gate/auth/NoAuthIT.kt | 3 +- .../controller/PartnerUploadControllerIT.kt | 29 +++++++++++++++++++ 12 files changed, 106 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0449e5a24..6ddd4e7f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ For changes to the BPDM Helm charts please consult the [changelog](charts/bpdm/C ### Added - BPDM Gate: Post endpoint to upload business partner input data using csv file.(#700) +- BPDM Gate: GET endpoint to download the csv file template for business partner upload. (#700) ## [6.0.1] - [2024-05-27] diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt index c79b57aa9..ce76b52bb 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt @@ -27,8 +27,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses import org.eclipse.tractusx.bpdm.gate.api.GateBusinessPartnerApi.Companion.BUSINESS_PARTNER_PATH import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto import org.eclipse.tractusx.bpdm.gate.api.model.response.PartnerUploadErrorResponse +import org.springframework.core.io.ByteArrayResource import org.springframework.http.MediaType import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestPart @@ -63,4 +65,17 @@ interface GatePartnerUploadApi { @RequestPart("file") file: MultipartFile ): ResponseEntity> + @Operation( + summary = "Get CSV template with headers in file", + description = "Create empty CSV file template including headers." + + "Generated CSV file can later be used for uploading business partner data." + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "CSV file template generated successfully") + ] + ) + @GetMapping("/input/partner-upload-template") + fun getPartnerCsvTemplate(): ResponseEntity + } \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/PartnerUploadApiClient.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/PartnerUploadApiClient.kt index 4a86bca19..6252ccd3a 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/PartnerUploadApiClient.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/PartnerUploadApiClient.kt @@ -21,10 +21,12 @@ package org.eclipse.tractusx.bpdm.gate.api.client import org.eclipse.tractusx.bpdm.gate.api.GatePartnerUploadApi import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto +import org.springframework.core.io.ByteArrayResource import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RequestPart import org.springframework.web.multipart.MultipartFile +import org.springframework.web.service.annotation.GetExchange import org.springframework.web.service.annotation.HttpExchange import org.springframework.web.service.annotation.PostExchange @@ -40,4 +42,9 @@ interface PartnerUploadApiClient : GatePartnerUploadApi { @RequestPart("file") file: MultipartFile ): ResponseEntity> + @GetExchange( + url = "/input/partner-upload-template", + ) + override fun getPartnerCsvTemplate(): ResponseEntity + } \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadController.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadController.kt index 576b0b06f..38e524a1b 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadController.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadController.kt @@ -26,7 +26,10 @@ import org.eclipse.tractusx.bpdm.gate.config.PermissionConfigProperties import org.eclipse.tractusx.bpdm.gate.service.BusinessPartnerService import org.eclipse.tractusx.bpdm.gate.service.PartnerUploadService import org.eclipse.tractusx.bpdm.gate.util.getCurrentUserBpn +import org.springframework.core.io.ByteArrayResource +import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.RestController @@ -50,4 +53,13 @@ class PartnerUploadController( } } + @PreAuthorize("hasAuthority(${PermissionConfigProperties.UPLOAD_INPUT_PARTNER})") + override fun getPartnerCsvTemplate(): ResponseEntity { + val resource = partnerUploadService.generatePartnerCsvTemplate() + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=partner-upload-template.csv") + .contentType(MediaType.parseMediaType("text/csv")) + .body(resource) + } + } \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt index c00ffe0be..84b89b1f0 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt @@ -19,13 +19,18 @@ package org.eclipse.tractusx.bpdm.gate.service +import com.opencsv.CSVWriter import mu.KotlinLogging import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto +import org.eclipse.tractusx.bpdm.gate.model.PartnerUploadFileHeader import org.eclipse.tractusx.bpdm.gate.model.PartnerUploadFileRow import org.eclipse.tractusx.bpdm.gate.util.PartnerFileUtil +import org.springframework.core.io.ByteArrayResource import org.springframework.http.ResponseEntity import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile +import java.io.ByteArrayOutputStream +import java.io.OutputStreamWriter @Service class PartnerUploadService( @@ -41,4 +46,24 @@ class PartnerUploadService( return ResponseEntity.ok(result) } + fun generatePartnerCsvTemplate(): ByteArrayResource { + val headers = PartnerUploadFileHeader::class.java.declaredFields + .asSequence() + .filter { it.name != "INSTANCE" && it.type == String::class.java } + .map { it.get(null) as String } + .toList() + .toTypedArray() + + // Use ByteArrayOutputStream with CSVWriter to create CSV content + val outputStream = ByteArrayOutputStream().apply { + OutputStreamWriter(this).use { writer -> + CSVWriter(writer).use { csvWriter -> + csvWriter.writeNext(headers) + } + } + } + + return ByteArrayResource(outputStream.toByteArray()) + } + } \ No newline at end of file diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt index 719ff8a75..7d7506435 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt @@ -63,7 +63,8 @@ class AuthAdminIT @Autowired constructor( getConfidenceCriteria = AuthExpectationType.Authorized ), uploadPartner = UploadPartnerAuthExpections( - postInput = AuthExpectationType.Authorized + postInput = AuthExpectationType.Authorized, + getInputTemplate = AuthExpectationType.Authorized ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt index cad57b87f..f77a96d0a 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt @@ -63,7 +63,8 @@ class AuthInputConsumerIT @Autowired constructor( getConfidenceCriteria = AuthExpectationType.Authorized ), uploadPartner = UploadPartnerAuthExpections( - postInput = AuthExpectationType.Forbidden + postInput = AuthExpectationType.Forbidden, + getInputTemplate = AuthExpectationType.Forbidden ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt index 3d1bcaee8..f7a47ee47 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt @@ -63,7 +63,8 @@ class AuthInputManagerIT @Autowired constructor( getConfidenceCriteria = AuthExpectationType.Authorized ), uploadPartner = UploadPartnerAuthExpections( - postInput = AuthExpectationType.Authorized + postInput = AuthExpectationType.Authorized, + getInputTemplate = AuthExpectationType.Authorized ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt index b98e54369..ea27c73d7 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt @@ -63,7 +63,8 @@ class AuthOutputConsumerIT @Autowired constructor( getConfidenceCriteria = AuthExpectationType.Authorized ), uploadPartner = UploadPartnerAuthExpections( - postInput = AuthExpectationType.Forbidden + postInput = AuthExpectationType.Forbidden, + getInputTemplate = AuthExpectationType.Forbidden ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt index 868a078df..daea091c6 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt @@ -101,6 +101,11 @@ abstract class AuthTestBase( authAssertions.assert(authExpectations.uploadPartner.postInput) { gateClient.partnerUpload.uploadPartnerCsvFile(uploadedFile) } } + @Test + fun `GET Partner Upload Template`() { + authAssertions.assert(authExpectations.uploadPartner.getInputTemplate) { gateClient.partnerUpload.getPartnerCsvTemplate() } + } + } data class GateAuthExpectations( @@ -135,5 +140,6 @@ data class StatsAuthExpectations( ) data class UploadPartnerAuthExpections( - val postInput: AuthExpectationType + val postInput: AuthExpectationType, + val getInputTemplate: AuthExpectationType ) diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt index db05c7b7c..9ff91faa2 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt @@ -61,7 +61,8 @@ class NoAuthIT @Autowired constructor( getConfidenceCriteria = AuthExpectationType.Unauthorized ), uploadPartner = UploadPartnerAuthExpections( - postInput = AuthExpectationType.Unauthorized + postInput = AuthExpectationType.Unauthorized, + getInputTemplate = AuthExpectationType.Unauthorized ) ) ) diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt index 64528782d..2d4d0caec 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt @@ -157,6 +157,35 @@ class PartnerUploadControllerIT @Autowired constructor( this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(searchResponsePage, expectedResponse) } + @Test + fun testGetCsvTemplateAndUploadWithExistingRecords() { + // Fetch the CSV template + val templateResponse = gateClient.partnerUpload.getPartnerCsvTemplate().body!! + val templateBytes = templateResponse.inputStream.readAllBytes() + val templateCsv = String(templateBytes) + + // Read the existing records from an existing test data file + val existingTestRecordsPath = Paths.get("src/test/resources/testData/valid_partner_data.csv") + val existingTestRecords = Files.readString(existingTestRecordsPath) + + // Combine the header from the template with the existing records + val combinedCsv = templateCsv + existingTestRecords.lines().drop(1).joinToString("\n") + + // Upload the combined CSV file + val combinedFile = MockMultipartFile("combined_partner_data.csv", "combined_partner_data.csv", "text/csv", combinedCsv.toByteArray()) + val uploadResponse = gateClient.partnerUpload.uploadPartnerCsvFile(combinedFile).body!! + + // Perform assertions + val expectedResponse = listOf( + BusinessPartnerVerboseValues.bpInputRequestFull, + BusinessPartnerNonVerboseValues.bpInputRequestFull.fastCopy(externalId = BusinessPartnerVerboseValues.externalId2, shortName = "2") + ) + + val searchResponsePage = gateClient.businessParters.getBusinessPartnersInput(listOf(BusinessPartnerVerboseValues.externalId1, BusinessPartnerVerboseValues.externalId2)).content + this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(uploadResponse, expectedResponse) + this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(searchResponsePage, expectedResponse) + } + private fun testFileUpload(filePath: String, expectedStatus: HttpStatus) { val bytes = Files.readAllBytes(Paths.get(filePath))