Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TACKLE-268]-Request failed with status 500 on importing invalid csv file #107

Merged
merged 11 commits into from
Aug 23, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,155 @@
import io.tackle.commons.annotations.Filterable;
import io.tackle.commons.entities.AbstractEntity;

import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.Size;

@Entity
@Table(name = "application_import")
public class ApplicationImport extends AbstractEntity {
public static final int APP_NAME_MAX_LENGTH = 120;

private String recordType1;

@Size(max = APP_NAME_MAX_LENGTH)
private String applicationName;

@Size(max = 250)
private String description;

@Size(max = 250)
private String comments;

@Size(max = 120)
private String businessService;

@Size(max = 40)
private String tagType1;

@Size(max = 40)
private String tag1;

@Size(max = 40)
private String tagType2;

@Size(max = 40)
private String tag2;

@Size(max = 40)
private String tagType3;

@Size(max = 40)
private String tag3;

@Size(max = 40)
private String tagType4;

@Size(max = 40)
private String tag4;

@Size(max = 40)
private String tagType5;

@Size(max = 40)
private String tag5;

@Size(max = 40)
private String tagType6;

@Size(max = 40)
private String tag6;

@Size(max = 40)
private String tagType7;

@Size(max = 40)
private String tag7;

@Size(max = 40)
private String tagType8;

@Size(max = 40)
private String tag8;

@Size(max = 40)
private String tagType9;

@Size(max = 40)
private String tag9;

@Size(max = 40)
private String tagType10;

@Size(max = 40)
private String tag10;

@Size(max = 40)
private String tagType11;

@Size(max = 40)
private String tag11;

@Size(max = 40)
private String tagType12;

@Size(max = 40)
private String tag12;

@Size(max = 40)
private String tagType13;

@Size(max = 40)
private String tag13;

@Size(max = 40)
private String tagType14;

@Size(max = 40)
private String tag14;

@Size(max = 40)
private String tagType15;

@Size(max = 40)
private String tag15;

@Size(max = 40)
private String tagType16;

@Size(max = 40)
private String tag16;

@Size(max = 40)
private String tagType17;

@Size(max = 40)
private String tag17;

@Size(max = 40)
private String tagType18;

@Size(max = 40)
private String tag18;

@Size(max = 40)
private String tagType19;

@Size(max = 40)
private String tag19;

@Size(max = 40)
private String tagType20;

@Size(max = 40)
private String tag20;

private String errorMessage;

@Filterable(check = CheckType.EQUAL)
public Boolean isValid = true;

@Filterable
public String filename;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.tackle.applicationinventory.entities.ImportSummary;
import io.tackle.applicationinventory.mapper.ApplicationInventoryAPIMapper;
import io.tackle.applicationinventory.mapper.ApplicationMapper;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;

Expand All @@ -20,6 +21,7 @@
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import javax.transaction.UserTransaction;
import javax.validation.Validator;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
Expand Down Expand Up @@ -53,6 +55,9 @@ public class ImportService {
@RestClient
BusinessServiceService businessServiceService;

@Inject
Validator validator;

@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
Expand Down Expand Up @@ -121,14 +126,28 @@ public Response importFile(@MultipartForm MultipartImportBody data) {
private List<ApplicationImport> writeFile(String content, String filename, ImportSummary parentObject) throws IOException {

MappingIterator<ApplicationImport> iter = decode(content);
List<ApplicationImport> importList = new ArrayList();
List<ApplicationImport> importList = new ArrayList<>();
while (iter.hasNext())
{
ApplicationImport importedApplication = iter.next();
importedApplication.setFilename(filename);
importedApplication.importSummary = parentObject;
importList.add(importedApplication);
importedApplication.persistAndFlush();

ApplicationImport appToPersist;
if (validator.validate(importedApplication).isEmpty()) {
appToPersist = importedApplication;

importList.add(appToPersist);
} else {
String truncatedAppName = StringUtils.truncate(importedApplication.getApplicationName().trim(), ApplicationImport.APP_NAME_MAX_LENGTH);;

appToPersist = new ApplicationImport();
appToPersist.setApplicationName(truncatedAppName);
appToPersist.setValid(false);
appToPersist.setErrorMessage("Max length error: one or more column's max length were exceeded");
}

appToPersist.setFilename(filename);
appToPersist.importSummary = parentObject;
appToPersist.persistAndFlush();
}
return importList;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ protected void testImportServiceCaseInsensitiveColumnHeaders() {
@Order(8)
protected void testImportServiceNoTagsRetrieved() {

WireMock.stubFor(get(urlPathEqualTo("/controls/tag"))
final StubMapping tagStubMapping = WireMock.stubFor(get(urlPathEqualTo("/controls/tag"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("")));
Expand Down Expand Up @@ -469,11 +469,8 @@ protected void testImportServiceNoTagsRetrieved() {
.log().body()
.body("[0].'errorMessage'", is("Unable to retrieve TagTypes from remote resource"));




removeTestObjects(Collections.emptyList());

WireMock.removeStub(tagStubMapping);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package io.tackle.applicationinventory.services.issues;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.common.ResourceArg;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import io.restassured.config.EncoderConfig;
import io.restassured.http.ContentType;
import io.tackle.applicationinventory.services.WireMockControlsServices;
import io.tackle.commons.testcontainers.KeycloakTestResource;
import io.tackle.commons.testcontainers.PostgreSQLDatabaseTestResource;
import io.tackle.commons.tests.SecuredResourceTest;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import javax.ws.rs.core.MediaType;
import java.io.File;
import java.util.Arrays;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;

@QuarkusTest
@QuarkusTestResource(value = PostgreSQLDatabaseTestResource.class,
initArgs = {
@ResourceArg(name = PostgreSQLDatabaseTestResource.DB_NAME, value = "application_inventory_db"),
@ResourceArg(name = PostgreSQLDatabaseTestResource.USER, value = "application_inventory"),
@ResourceArg(name = PostgreSQLDatabaseTestResource.PASSWORD, value = "application_inventory")
}
)
@QuarkusTestResource(value = KeycloakTestResource.class,
initArgs = {
@ResourceArg(name = KeycloakTestResource.IMPORT_REALM_JSON_PATH, value = "keycloak/quarkus-realm.json"),
@ResourceArg(name = KeycloakTestResource.REALM_NAME, value = "quarkus")
}
)
@QuarkusTestResource(WireMockControlsServices.class)
// https://issues.redhat.com/browse/TACKLE-268
public class IssueTACKLE268Test extends SecuredResourceTest {

@BeforeAll
public static void init() {
PATH = "/file/upload";
}

@Test
protected void testImportServiceLongCSVColumnValues() {
ClassLoader classLoader = getClass().getClassLoader();
File importFile = new File(classLoader.getResource("long_characters_columns.csv").getFile());

given()
.config(RestAssured.config().encoderConfig(EncoderConfig.encoderConfig().encodeContentTypeAs("multipart/form-data", ContentType.JSON)))
.contentType(MediaType.MULTIPART_FORM_DATA)
.accept(MediaType.MULTIPART_FORM_DATA)
.multiPart("file", importFile)
.multiPart("fileName", "long_characters_columns.csv")
.when().post(PATH)
.then()
.statusCode(200);

final long importSummaryId = Long.parseLong(given()
.accept("application/hal+json")
.when()
.get("/import-summary")
.then()
.statusCode(200)
.body("_embedded.import-summary.size()", is(1),
"_embedded.import-summary[0].importStatus", is("Completed"),
"_embedded.import-summary[0].validCount", is(1),
"_embedded.import-summary[0].invalidCount", is(2))
.extract().path("_embedded.import-summary[0].id").toString());

final String csv = given()
.accept("text/csv")
.queryParam("importSummaryId", importSummaryId)
.when()
.get("/csv-export")
.body()
.print();
String[] csvFields = csv.split(",");

int numberOfRows = (int) Arrays.stream(csvFields).filter("\n"::equals).count();
assertEquals(2, numberOfRows);

assertEquals(1, (int) Arrays.stream(csvFields).filter("Import-app-9"::equals).count());
assertEquals(1, Arrays.stream(csvFields).filter(f -> f.startsWith("\"Very-long-app-name-name-")).count());

// Clean test data to not alter other tests execution
// Remove the successfully imported 'Import-app-8' application
final long importApp8Id = Long.parseLong(given()
.queryParam("name", "Import-app-8")
.accept(ContentType.JSON)
.when()
.get("/application")
.then()
.statusCode(200)
.body("size()", is(1))
.extract()
.path("[0].id")
.toString());

given()
.accept(ContentType.JSON)
.pathParam("id", importApp8Id)
.when()
.delete("/application/{id}")
.then()
.statusCode(204);

// Remove the import summary record (on cascade also the ApplicationImport entities will be deleted)
given()
.accept(ContentType.JSON)
.pathParam("id", importSummaryId)
.when()
.delete("/import-summary/{id}")
.then()
.statusCode(204);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.tackle.applicationinventory.services.issues;

import io.quarkus.test.junit.NativeImageTest;

@NativeImageTest
public class NativeIssueTACKLE268IT extends IssueTACKLE268Test {}
4 changes: 4 additions & 0 deletions src/test/resources/long_characters_columns.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3,Tag Type 4,Tag 4,Tag Type 5,Tag 5,Tag Type 6,Tag 6,Tag Type 7,Tag 7,Tag Type 8,Tag 8,Tag Type 9,Tag 9,Tag Type 10,Tag 10,Tag Type 11,Tag 11,Tag Type 12,Tag 12,Tag Type 13,Tag 13,Tag Type 14,Tag 14,Tag Type 15,Tag 15,Tag Type 16,Tag 16,Tag Type 17,Tag 17,Tag Type 18,Tag 18,Tag Type 19,Tag 19,Tag Type 20,Tag 20
,Import-app-8,Food vendors maintain,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,Import-app-9,Description for imported app,Food vendors maintain their meFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via Orderhubnu and price data which is curated and presented to customers via Orderhub,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,Very-long-app-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name,Description,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,