Skip to content

Commit

Permalink
feat(bpdm-gate): GET endpoint to download csv file template for busin…
Browse files Browse the repository at this point in the history
…ess partner upload process
  • Loading branch information
SujitMBRDI committed Jun 21, 2024
1 parent 5cab721 commit d660128
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -63,4 +65,17 @@ interface GatePartnerUploadApi {
@RequestPart("file") file: MultipartFile
): ResponseEntity<Collection<BusinessPartnerInputDto>>

@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<ByteArrayResource>

}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -40,4 +42,9 @@ interface PartnerUploadApiClient : GatePartnerUploadApi {
@RequestPart("file") file: MultipartFile
): ResponseEntity<Collection<BusinessPartnerInputDto>>

@GetExchange(
url = "/input/partner-upload-template",
)
override fun getPartnerCsvTemplate(): ResponseEntity<ByteArrayResource>

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -50,4 +53,13 @@ class PartnerUploadController(
}
}

@PreAuthorize("hasAuthority(${PermissionConfigProperties.UPLOAD_INPUT_PARTNER})")
override fun getPartnerCsvTemplate(): ResponseEntity<ByteArrayResource> {
val resource = partnerUploadService.generatePartnerCsvTemplate()
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=partner-upload-template.csv")
.contentType(MediaType.parseMediaType("text/csv"))
.body(resource)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class AuthAdminIT @Autowired constructor(
getConfidenceCriteria = AuthExpectationType.Authorized
),
uploadPartner = UploadPartnerAuthExpections(
postInput = AuthExpectationType.Authorized
postInput = AuthExpectationType.Authorized,
getInputTemplate = AuthExpectationType.Authorized
)
)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class AuthInputConsumerIT @Autowired constructor(
getConfidenceCriteria = AuthExpectationType.Authorized
),
uploadPartner = UploadPartnerAuthExpections(
postInput = AuthExpectationType.Forbidden
postInput = AuthExpectationType.Forbidden,
getInputTemplate = AuthExpectationType.Forbidden
)
)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class AuthInputManagerIT @Autowired constructor(
getConfidenceCriteria = AuthExpectationType.Authorized
),
uploadPartner = UploadPartnerAuthExpections(
postInput = AuthExpectationType.Authorized
postInput = AuthExpectationType.Authorized,
getInputTemplate = AuthExpectationType.Authorized
)
)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class AuthOutputConsumerIT @Autowired constructor(
getConfidenceCriteria = AuthExpectationType.Authorized
),
uploadPartner = UploadPartnerAuthExpections(
postInput = AuthExpectationType.Forbidden
postInput = AuthExpectationType.Forbidden,
getInputTemplate = AuthExpectationType.Forbidden
)
)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -135,5 +140,6 @@ data class StatsAuthExpectations(
)

data class UploadPartnerAuthExpections(
val postInput: AuthExpectationType
val postInput: AuthExpectationType,
val getInputTemplate: AuthExpectationType
)
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ class NoAuthIT @Autowired constructor(
getConfidenceCriteria = AuthExpectationType.Unauthorized
),
uploadPartner = UploadPartnerAuthExpections(
postInput = AuthExpectationType.Unauthorized
postInput = AuthExpectationType.Unauthorized,
getInputTemplate = AuthExpectationType.Unauthorized
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit d660128

Please sign in to comment.