diff --git a/USAGE.md b/USAGE.md index 4fcf9c8..9f145af 100644 --- a/USAGE.md +++ b/USAGE.md @@ -123,3 +123,19 @@ the payload obtained will look like this : ] ``` +## `/file/upload` endpoint +This endpoint will upload a csv file of the format of this file: +`src/test/resources/sample_application_import.csv` + +The upload process stores each row from the file as an ApplicationImport entity. +Each ApplicationImport is then used to attempt to create a new Application. + +The endpoint expects a Multipart Form with these params: +`file`: the file to be imported, in json format. +`fileName`: A text identifier for the file to be stored under. + +## `/application-import` endpoint +This endpoint can be used to retrieve ApplicationImports. The results retrieved can be filtered by +`isValid=false` or `isValid=true` to view those imports which have either failed or succeeded, +and by `filename=your_filename` to view results for a specific import attempt. + diff --git a/pom.xml b/pom.xml index f6d54e7..0910360 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,14 @@ io.quarkus quarkus-resteasy-jackson + + io.quarkus + quarkus-resteasy-multipart + + + com.fasterxml.jackson.dataformat + jackson-dataformat-csv + io.quarkus quarkus-smallrye-openapi @@ -105,6 +113,18 @@ io.quarkus quarkus-oidc + + io.quarkus + quarkus-oidc-token-propagation + + + io.quarkus + quarkus-rest-client + + + io.quarkus + quarkus-rest-client-jackson + io.quarkus quarkus-keycloak-authorization @@ -166,6 +186,17 @@ 2.6.0 test + + org.mockito + mockito-core + 3.8.0 + test + + + io.quarkus + quarkus-junit5-mockito + test + @@ -199,6 +230,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 10 + 10 + + diff --git a/src/main/java/io/tackle/applicationinventory/BusinessService.java b/src/main/java/io/tackle/applicationinventory/BusinessService.java new file mode 100644 index 0000000..386df35 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/BusinessService.java @@ -0,0 +1,9 @@ +package io.tackle.applicationinventory; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BusinessService { + public String id; + public String name; +} diff --git a/src/main/java/io/tackle/applicationinventory/MultipartImportBody.java b/src/main/java/io/tackle/applicationinventory/MultipartImportBody.java new file mode 100644 index 0000000..ac73b3a --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/MultipartImportBody.java @@ -0,0 +1,36 @@ +package io.tackle.applicationinventory; + +import javax.ws.rs.FormParam; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.annotations.providers.multipart.PartType; + +public class MultipartImportBody { + @FormParam("file") + @PartType(MediaType.APPLICATION_JSON) + private String file; + @FormParam("fileName") + @PartType(MediaType.TEXT_PLAIN) + private String fileName; + + public MultipartImportBody() {} + + + public void setFile(String file) + { + this.file = file; + } + + public void setFilename(String fileName) + { + this.fileName = fileName; + } + + public String getFile() { + return file; + } + + public String getFileName() { + return fileName; + } +} diff --git a/src/main/java/io/tackle/applicationinventory/Tag.java b/src/main/java/io/tackle/applicationinventory/Tag.java new file mode 100644 index 0000000..da2924f --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/Tag.java @@ -0,0 +1,17 @@ +package io.tackle.applicationinventory; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Tag { + public String id; + public String name; + public TagType tagType; + + public static class TagType { + public String id; + public String name; + } +} diff --git a/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java new file mode 100644 index 0000000..96c7b48 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java @@ -0,0 +1,515 @@ +package io.tackle.applicationinventory.entities; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSetter; +import io.tackle.commons.annotations.CheckType; +import io.tackle.commons.annotations.Filterable; +import io.tackle.commons.entities.AbstractEntity; + +import javax.persistence.*; + +@Entity +@Table(name = "application_import") +public class ApplicationImport extends AbstractEntity { + private String recordType1; + private String applicationName; + private String description; + private String comments; + private String businessService; + private String tagType1; + private String tag1; + private String tagType2; + private String tag2; + private String tagType3; + private String tag3; + private String tagType4; + private String tag4; + private String tagType5; + private String tag5; + private String tagType6; + private String tag6; + private String tagType7; + private String tag7; + private String tagType8; + private String tag8; + private String tagType9; + private String tag9; + private String tagType10; + private String tag10; + private String tagType11; + private String tag11; + private String tagType12; + private String tag12; + private String tagType13; + private String tag13; + private String tagType14; + private String tag14; + private String tagType15; + private String tag15; + private String tagType16; + private String tag16; + private String tagType17; + private String tag17; + private String tagType18; + private String tag18; + private String tagType19; + private String tag19; + private String tagType20; + private String tag20; + private String errorMessage; + @Filterable(check = CheckType.EQUAL) + public Boolean isValid = true; + @Filterable + public String filename; + + private String status; + @ManyToOne(optional = false) + @JsonIgnore + @Filterable(filterName = "importSummary.id") + public ImportSummary importSummary; + + public ApplicationImport() { + + } + + public String getRecordType1() { + return recordType1; + } + + @JsonSetter("Record Type 1") + public void setRecordType1(String recordType1) { + this.recordType1 = recordType1; + } + + public String getApplicationName() { + return applicationName; + } + + @JsonSetter("Application Name") + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getDescription() { + return description; + } + + @JsonSetter("Description") + public void setDescription(String description) { + this.description = description; + } + + public String getComments() { + return comments; + } + + @JsonSetter("Comments") + public void setComments(String comments) { + this.comments = comments; + } + + public String getBusinessService() { + return businessService; + } + + @JsonSetter("Business Service") + public void setBusinessService(String businessService) { + this.businessService = businessService; + } + + public String getTagType1() { + return tagType1; + } + + @JsonSetter("Tag Type 1") + public void setTagType1(String tagType1) { + this.tagType1 = tagType1; + } + + public String getTag1() { + return tag1; + } + + @JsonSetter("Tag 1") + public void setTag1(String tag1) { + this.tag1 = tag1; + } + + public String getTagType2() { + return tagType2; + } + + @JsonSetter("Tag Type 2") + public void setTagType2(String tagType2) { + this.tagType2 = tagType2; + } + + public String getTag2() { + return tag2; + } + + @JsonSetter("Tag 2") + public void setTag2(String tag2) { + this.tag2 = tag2; + } + + public String getTagType3() { + return tagType3; + } + + @JsonSetter("Tag Type 3") + public void setTagType3(String tagType3) { + this.tagType3 = tagType3; + } + + public String getTag3() { + return tag3; + } + + @JsonSetter("Tag 3") + public void setTag3(String tag3) { + this.tag3 = tag3; + } + + public String getTagType4() { + return tagType4; + } + + @JsonSetter("Tag Type 4") + public void setTagType4(String tagType4) { + this.tagType4 = tagType4; + } + + public String getTag4() { + return tag4; + } + + @JsonSetter("Tag 4") + public void setTag4(String tag4) { + this.tag4 = tag4; + } + + public String getTagType5() { + return tagType5; + } + + @JsonSetter("Tag Type 5") + public void setTagType5(String tagType5) { + this.tagType5 = tagType5; + } + + public String getTag5() { + return tag5; + } + + @JsonSetter("Tag 5") + public void setTag5(String tag5) { + this.tag5 = tag5; + } + + public String getTagType6() { + return tagType6; + } + + @JsonSetter("Tag Type 6") + public void setTagType6(String tagType6) { + this.tagType6 = tagType6; + } + + public String getTag6() { + return tag6; + } + + @JsonSetter("Tag 6") + public void setTag6(String tag6) { + this.tag6 = tag6; + } + + public String getTagType7() { + return tagType7; + } + + @JsonSetter("Tag Type 7") + public void setTagType7(String tagType7) { + this.tagType7 = tagType7; + } + + public String getTag7() { + return tag7; + } + + @JsonSetter("Tag 7") + public void setTag7(String tag7) { + this.tag7 = tag7; + } + + public String getTagType8() { + return tagType8; + } + + @JsonSetter("Tag Type 8") + public void setTagType8(String tagType8) { + this.tagType8 = tagType8; + } + + public String getTag8() { + return tag8; + } + + @JsonSetter("Tag 8") + public void setTag8(String tag8) { + this.tag8 = tag8; + } + + public String getTagType9() { + return tagType9; + } + + @JsonSetter("Tag Type 9") + public void setTagType9(String tagType9) { + this.tagType9 = tagType9; + } + + public String getTag9() { + return tag9; + } + + @JsonSetter("Tag 9") + public void setTag9(String tag9) { + this.tag9 = tag9; + } + + public String getTagType10() { + return tagType10; + } + + @JsonSetter("Tag Type 10") + public void setTagType10(String tagType10) { + this.tagType10 = tagType10; + } + + public String getTag10() { + return tag10; + } + + @JsonSetter("Tag 10") + public void setTag10(String tag10) { + this.tag10 = tag10; + } + + public String getTagType11() { + return tagType11; + } + + @JsonSetter("Tag Type 11") + public void setTagType11(String tagType11) { + this.tagType11 = tagType11; + } + + public String getTag11() { + return tag11; + } + + @JsonSetter("Tag 11") + public void setTag11(String tag11) { + this.tag11 = tag11; + } + + public String getTagType12() { + return tagType12; + } + + @JsonSetter("Tag Type 12") + public void setTagType12(String tagType12) { + this.tagType12 = tagType12; + } + + public String getTag12() { + return tag12; + } + + @JsonSetter("Tag 12") + public void setTag12(String tag12) { + this.tag12 = tag12; + } + + public String getTagType13() { + return tagType13; + } + + @JsonSetter("Tag Type 13") + public void setTagType13(String tagType13) { + this.tagType13 = tagType13; + } + + public String getTag13() { + return tag13; + } + + @JsonSetter("Tag 13") + public void setTag13(String tag13) { + this.tag13 = tag13; + } + + public String getTagType14() { + return tagType14; + } + + @JsonSetter("Tag Type 14") + public void setTagType14(String tagType14) { + this.tagType14 = tagType14; + } + + public String getTag14() { + return tag14; + } + + @JsonSetter("Tag 14") + public void setTag14(String tag14) { + this.tag14 = tag14; + } + + public String getTagType15() { + return tagType15; + } + + @JsonSetter("Tag Type 15") + public void setTagType15(String tagType15) { + this.tagType15 = tagType15; + } + + public String getTag15() { + return tag15; + } + + @JsonSetter("Tag 15") + public void setTag15(String tag15) { + this.tag15 = tag15; + } + + public String getTagType16() { + return tagType16; + } + + @JsonSetter("Tag Type 16") + public void setTagType16(String tagType16) { + this.tagType16 = tagType16; + } + + public String getTag16() { + return tag16; + } + + @JsonSetter("Tag 16") + public void setTag16(String tag16) { + this.tag16 = tag16; + } + + public String getTagType17() { + return tagType17; + } + + @JsonSetter("Tag Type 17") + public void setTagType17(String tagType17) { + this.tagType17 = tagType17; + } + + public String getTag17() { + return tag17; + } + + @JsonSetter("Tag 17") + public void setTag17(String tag17) { + this.tag17 = tag17; + } + + public String getTagType18() { + return tagType18; + } + + @JsonSetter("Tag Type 18") + public void setTagType18(String tagType18) { + this.tagType18 = tagType18; + } + + public String getTag18() { + return tag18; + } + + @JsonSetter("Tag 18") + public void setTag18(String tag18) { + this.tag18 = tag18; + } + + public String getTagType19() { + return tagType19; + } + + @JsonSetter("Tag Type 19") + public void setTagType19(String tagType19) { + this.tagType19 = tagType19; + } + + public String getTag19() { + return tag19; + } + + @JsonSetter("Tag 19") + public void setTag19(String tag19) { + this.tag19 = tag19; + } + + public String getTagType20() { + return tagType20; + } + + @JsonSetter("Tag Type 20") + public void setTagType20(String tagType20) { + this.tagType20 = tagType20; + } + + public String getTag20() { + return tag20; + } + + @JsonSetter("Tag 20") + public void setTag20(String tag20) { + this.tag20 = tag20; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public Boolean getValid() { + return isValid; + } + + public void setValid(Boolean valid) { + isValid = valid; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + +} + + + diff --git a/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java new file mode 100644 index 0000000..66db63a --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java @@ -0,0 +1,103 @@ +package io.tackle.applicationinventory.entities; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +@JsonIgnoreProperties({ "createUser", "updateUser", "errorMessage", "valid", "isValid", "filename", "status", "id"}) +public abstract class ApplicationImportForCsv { + @JsonProperty("Record Type 1") + private String recordType1; + @JsonProperty("Application Name") + private String applicationName; + @JsonProperty("Description") + private String description; + @JsonProperty("Comments") + private String comments; + @JsonProperty("Business Service") + private String businessService; + @JsonProperty("Tag Type 1") + private String tagType1; + @JsonProperty("Tag 1") + private String tag1; + @JsonProperty("Tag Type 2") + private String tagType2; + @JsonProperty("Tag 2") + private String tag2; + @JsonProperty("Tag Type 3") + private String tagType3; + @JsonProperty("Tag 3") + private String tag3; + @JsonProperty("Tag Type 4") + private String tagType4; + @JsonProperty("Tag 4") + private String tag4; + @JsonProperty("Tag Type 5") + private String tagType5; + @JsonProperty("Tag 5") + private String tag5; + @JsonProperty("Tag Type 6") + private String tagType6; + @JsonProperty("Tag 6") + private String tag6; + @JsonProperty("Tag Type 7") + private String tagType7; + @JsonProperty("Tag 7") + private String tag7; + @JsonProperty("Tag Type 8") + private String tagType8; + @JsonProperty("Tag 8") + private String tag8; + @JsonProperty("Tag Type 9") + private String tagType9; + @JsonProperty("Tag 9") + private String tag9; + @JsonProperty("Tag Type 10") + private String tagType10; + @JsonProperty("Tag 10") + private String tag10; + @JsonProperty("Tag Type 11") + private String tagType11; + @JsonProperty("Tag 11") + private String tag11; + @JsonProperty("Tag Type 12") + private String tagType12; + @JsonProperty("Tag 12") + private String tag12; + @JsonProperty("Tag Type 13") + private String tagType13; + @JsonProperty("Tag 13") + private String tag13; + @JsonProperty("Tag Type 14") + private String tagType14; + @JsonProperty("Tag 14") + private String tag14; + @JsonProperty("Tag Type 15") + private String tagType15; + @JsonProperty("Tag 15") + private String tag15; + @JsonProperty("Tag Type 16") + private String tagType16; + @JsonProperty("Tag 16") + private String tag16; + @JsonProperty("Tag Type 17") + private String tagType17; + @JsonProperty("Tag 17") + private String tag17; + @JsonProperty("Tag Type 18") + private String tagType18; + @JsonProperty("Tag 18") + private String tag18; + @JsonProperty("Tag Type 19") + private String tagType19; + @JsonProperty("Tag 19") + private String tag19; + @JsonProperty("Tag Type 20") + private String tagType20; + @JsonProperty("Tag 20") + private String tag20; +} + + + diff --git a/src/main/java/io/tackle/applicationinventory/entities/ImportSummary.java b/src/main/java/io/tackle/applicationinventory/entities/ImportSummary.java new file mode 100644 index 0000000..4c00048 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/entities/ImportSummary.java @@ -0,0 +1,47 @@ +package io.tackle.applicationinventory.entities; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.tackle.commons.annotations.Filterable; +import io.tackle.commons.entities.AbstractEntity; +import org.hibernate.annotations.CreationTimestamp; + +import javax.persistence.*; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Entity +@Table( + name = "import_summary" +) +public class ImportSummary extends AbstractEntity { + @Filterable + public String filename; + @Filterable + public String importStatus; + public String errorMessage; + @CreationTimestamp + @Column(updatable=false) + public Timestamp importTime; + @OneToMany(mappedBy = "importSummary", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) + @JsonIgnore + public List applicationImports = new ArrayList<>(); + + @Transient + public int invalidCount; + + @Transient + public int validCount; + + @PostLoad + private void setCounts() + { + invalidCount = applicationImports.stream().filter(applicationImport -> applicationImport.isValid.equals(Boolean.FALSE)).collect(Collectors.toList()).size(); + validCount = applicationImports.stream().filter(applicationImport -> applicationImport.isValid.equals(Boolean.TRUE)).collect(Collectors.toList()).size(); + } + + + +} diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationInventoryAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationInventoryAPIMapper.java new file mode 100644 index 0000000..025051f --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationInventoryAPIMapper.java @@ -0,0 +1,130 @@ +package io.tackle.applicationinventory.mapper; + +import io.tackle.applicationinventory.BusinessService; +import io.tackle.applicationinventory.Tag; +import io.tackle.applicationinventory.entities.Application; +import io.tackle.applicationinventory.entities.ApplicationImport; + +import javax.ws.rs.core.Response; +import java.lang.reflect.InvocationTargetException; +import java.util.Set; +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + + +public class ApplicationInventoryAPIMapper extends ApplicationMapper{ + + public ApplicationInventoryAPIMapper( Set tags, Set businessServices) { + super(tags, businessServices); + } + + @Override + public Response map(ApplicationImport importApp, Long parentId) + { + //importApp.setParentId(parentId); + Application newApp = new Application(); + Set tags = new HashSet<>(); + + try { + if (importApp.getBusinessService() != null && !importApp.getBusinessService().isEmpty()) { + newApp.businessService = addBusinessService(importApp.getBusinessService()); + } else { + importApp.setErrorMessage("Business Service is Mandatory"); + return Response.serverError().build(); + } + + } + catch(NoSuchElementException nsee1){ + nsee1.printStackTrace(); + importApp.setErrorMessage("Business Service: " + importApp.getBusinessService() + " does not exist"); + return Response.serverError().build(); + } + newApp.comments = importApp.getComments(); + + if (importApp.getDescription() != null && !importApp.getDescription().isEmpty()) { + newApp.description = importApp.getDescription(); + } else { + importApp.setErrorMessage("Description is Mandatory"); + return Response.serverError().build(); + } + + // check for duplicates on table + long count = Application.count("name",importApp.getApplicationName()); + if(count>0) + { + importApp.setErrorMessage("Duplicate ApplicationName in table: " + importApp.getApplicationName()); + return Response.serverError().build(); + } + newApp.name = importApp.getApplicationName(); + String currentTag = ""; + String currentTagType = ""; + try{ + for(int i=1;i<=20;i++) + { + String tagMethodName = "getTag" + i; + String tagTypeMethodName = "getTagType" + i; + java.lang.reflect.Method tagMethod; + java.lang.reflect.Method tagTypeMethod; + tagMethod = importApp.getClass().getMethod(tagMethodName); + tagTypeMethod = importApp.getClass().getMethod(tagTypeMethodName); + currentTag = (String)tagMethod.invoke(importApp); + currentTagType = (String)tagTypeMethod.invoke(importApp); + if ((currentTag == null || currentTag.isEmpty()) + && (currentTagType == null || currentTagType.isEmpty())) { + //don't validate and add tag/tagtype if both aren't present + } + else{ + tags.add(addTag(currentTag, currentTagType)); + } + + } + + } + catch(NoSuchElementException nsee3) + { + nsee3.printStackTrace(); + importApp.setErrorMessage("Tag Type " + currentTagType + " and Tag " + currentTag + " combination does not exist"); + return Response.serverError().build(); + } + catch (SecurityException |NoSuchMethodException | IllegalAccessException | InvocationTargetException e) + { + e.printStackTrace(); + importApp.setErrorMessage("Tag Type " + currentTagType + " and Tag " + currentTag + " unable to perform validation"); + return Response.serverError().build(); + } + // try { + newApp.tags = tags; + newApp.persistAndFlush(); + /** } + catch(Exception e){ + e.printStackTrace(); + importApp.setErrorMessage("Duplicate ApplicationName in table: " + importApp.getApplicationName()); + return Response.serverError().build(); + }*/ + return Response.ok().build(); + } + + + private String addBusinessService(String businessServiceName) throws NoSuchElementException + { + Optional businessServiceOptional = businessServices.stream().filter(businessServiceControls -> businessServiceControls.name.equals(businessServiceName)) + .findFirst(); + + + return businessServiceOptional.orElseThrow().id; + + } + + private String addTag(String tagName, String tagTypeName) throws NoSuchElementException + { + List tagList = tags.stream().filter(controlsTag -> controlsTag.name.equals(tagName)).collect(Collectors.toList()); + + Optional tagOptional = tagList.stream().filter(tagControls -> tagControls.tagType.name.equals(tagTypeName)) + .findFirst(); + + return tagOptional.orElseThrow().id; + } +} diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationMapper.java new file mode 100644 index 0000000..897ffff --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationMapper.java @@ -0,0 +1,24 @@ +package io.tackle.applicationinventory.mapper; + +import io.tackle.applicationinventory.BusinessService; +import io.tackle.applicationinventory.Tag; +import io.tackle.applicationinventory.entities.ApplicationImport; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.core.Response; +import java.util.Set; + +public abstract class ApplicationMapper { + @ApplicationScoped + Set tags; + @ApplicationScoped + Set businessServices; + + public ApplicationMapper(Set tags, Set businessServices) + { + this.tags = tags; + this.businessServices = businessServices; + } + + public abstract Response map(ApplicationImport importApp, Long parentId); +} diff --git a/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportListFilteredResource.java b/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportListFilteredResource.java new file mode 100644 index 0000000..89f71cc --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportListFilteredResource.java @@ -0,0 +1,49 @@ +package io.tackle.applicationinventory.resources; + +import io.tackle.applicationinventory.entities.ApplicationImport; +import io.tackle.commons.resources.ListFilteredResource; +import org.jboss.resteasy.links.LinkResource; + +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.List; + +@Path("application-import") +public class ApplicationImportListFilteredResource implements ListFilteredResource { + + @Override + public Class getPanacheEntityType() { + return ApplicationImport.class; + } + + @GET + @Path("") + @Produces({"application/json"}) + @LinkResource( + entityClassName = "io.tackle.applicationinventory.entities.ApplicationImport", + rel = "list" + ) + public Response list(@QueryParam(QUERY_PARAM_SORT) @DefaultValue(DEFAULT_VALUE_SORT) List var1, + @QueryParam(QUERY_PARAM_PAGE) @DefaultValue(DEFAULT_VALUE_PAGE) int var2, + @QueryParam(QUERY_PARAM_SIZE) @DefaultValue(DEFAULT_VALUE_SIZE) int var3, + @Context UriInfo var4) throws Exception { + return ListFilteredResource.super.list(var1, var2, var3, var4, false); + } + + @Path("") + @GET + @Produces({"application/hal+json"}) + public Response listHal(@QueryParam(QUERY_PARAM_SORT) @DefaultValue(DEFAULT_VALUE_SORT) List var1, + @QueryParam(QUERY_PARAM_PAGE) @DefaultValue(DEFAULT_VALUE_PAGE) int var2, + @QueryParam(QUERY_PARAM_SIZE) @DefaultValue(DEFAULT_VALUE_SIZE) int var3, + @Context UriInfo var4) throws Exception { + return ListFilteredResource.super.list(var1, var2, var3, var4, true); + } +} + diff --git a/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportResource.java b/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportResource.java new file mode 100644 index 0000000..02571ba --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportResource.java @@ -0,0 +1,16 @@ +package io.tackle.applicationinventory.resources; + +import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import io.quarkus.rest.data.panache.MethodProperties; +import io.quarkus.rest.data.panache.ResourceProperties; +import io.tackle.applicationinventory.entities.ApplicationImport; + +import java.util.List; + +@ResourceProperties(hal = true) +public interface ApplicationImportResource extends PanacheEntityResource { + @MethodProperties(exposed = false) + List list(Page page, Sort sort); +} diff --git a/src/main/java/io/tackle/applicationinventory/resources/CsvExportResource.java b/src/main/java/io/tackle/applicationinventory/resources/CsvExportResource.java new file mode 100644 index 0000000..2614609 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/resources/CsvExportResource.java @@ -0,0 +1,23 @@ +package io.tackle.applicationinventory.resources; + + +import io.tackle.applicationinventory.services.CsvExportService; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.*; + +@Path("csv-export") +@ApplicationScoped +public class CsvExportResource { + @Inject + CsvExportService csvExportService; + + + @Produces("text/csv") + @Consumes("application/json") + @GET + public String getCsvExportForImportSummaryId(@QueryParam("importSummaryId") Long id) { + return csvExportService.getCsvExportForImportSummaryId(id); + } +} diff --git a/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryListFilteredResource.java b/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryListFilteredResource.java new file mode 100644 index 0000000..9bb1b4c --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryListFilteredResource.java @@ -0,0 +1,45 @@ +package io.tackle.applicationinventory.resources; + +import io.tackle.applicationinventory.entities.ApplicationImport; +import io.tackle.applicationinventory.entities.ImportSummary; +import io.tackle.commons.resources.ListFilteredResource; +import org.jboss.resteasy.links.LinkResource; + +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.List; + +@Path("import-summary") +public class ImportSummaryListFilteredResource implements ListFilteredResource { + + @Override + public Class getPanacheEntityType() { + return ImportSummary.class; + } + + @GET + @Path("") + @Produces({"application/json"}) + @LinkResource( + entityClassName = "io.tackle.applicationinventory.entities.ImportSummary", + rel = "list" + ) + public Response list(@QueryParam(QUERY_PARAM_SORT) @DefaultValue(DEFAULT_VALUE_SORT) List var1, + @QueryParam(QUERY_PARAM_PAGE) @DefaultValue(DEFAULT_VALUE_PAGE) int var2, + @QueryParam(QUERY_PARAM_SIZE) @DefaultValue(DEFAULT_VALUE_SIZE) int var3, + @Context UriInfo var4) throws Exception { + return ListFilteredResource.super.list(var1, var2, var3, var4, false); + } + + @Path("") + @GET + @Produces({"application/hal+json"}) + public Response listHal(@QueryParam(QUERY_PARAM_SORT) @DefaultValue(DEFAULT_VALUE_SORT) List var1, + @QueryParam(QUERY_PARAM_PAGE) @DefaultValue(DEFAULT_VALUE_PAGE) int var2, + @QueryParam(QUERY_PARAM_SIZE) @DefaultValue(DEFAULT_VALUE_SIZE) int var3, + @Context UriInfo var4) throws Exception { + return ListFilteredResource.super.list(var1, var2, var3, var4, true); + } +} diff --git a/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryResource.java b/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryResource.java new file mode 100644 index 0000000..637c20a --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryResource.java @@ -0,0 +1,16 @@ +package io.tackle.applicationinventory.resources; + +import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; +import io.quarkus.rest.data.panache.MethodProperties; +import io.quarkus.rest.data.panache.ResourceProperties; +import io.tackle.applicationinventory.entities.ImportSummary; + +import java.util.List; + +@ResourceProperties(hal = true) +public interface ImportSummaryResource extends PanacheEntityResource { + @MethodProperties(exposed = false) + List list(Page page, Sort sort); +} diff --git a/src/main/java/io/tackle/applicationinventory/services/BusinessServiceService.java b/src/main/java/io/tackle/applicationinventory/services/BusinessServiceService.java new file mode 100644 index 0000000..6d18553 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/services/BusinessServiceService.java @@ -0,0 +1,21 @@ +package io.tackle.applicationinventory.services; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.GET; + +import io.quarkus.oidc.token.propagation.AccessToken; +import io.tackle.applicationinventory.BusinessService; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import java.util.Set; + + + +@RegisterRestClient() +@AccessToken +@ApplicationScoped +public interface BusinessServiceService { + + @GET + Set getListOfBusinessServices(); +} diff --git a/src/main/java/io/tackle/applicationinventory/services/CsvExportService.java b/src/main/java/io/tackle/applicationinventory/services/CsvExportService.java new file mode 100644 index 0000000..ab4d479 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/services/CsvExportService.java @@ -0,0 +1,33 @@ +package io.tackle.applicationinventory.services; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.dataformat.csv.CsvSchema; +import io.tackle.applicationinventory.entities.ApplicationImport; +import io.tackle.applicationinventory.entities.ApplicationImportForCsv; + +import javax.enterprise.context.ApplicationScoped; +import java.util.List; + +@ApplicationScoped +public class CsvExportService { + + public String getCsvExportForImportSummaryId(Long importSummaryId) + { + List importList = ApplicationImport.list("importSummary_id=?1 and isValid=?2", importSummaryId, false); + final CsvMapper mapper = new CsvMapper(); + mapper.disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); + final CsvSchema schema = mapper.schemaFor(ApplicationImportForCsv.class); + mapper.addMixIn(ApplicationImport.class, ApplicationImportForCsv.class); + + String csv = null; + try { + csv = mapper.writer(schema.withUseHeader(true)).writeValueAsString(importList); + }catch(JsonProcessingException jpe){ + jpe.printStackTrace(); + throw new RuntimeException(jpe); + } + return csv; + } +} diff --git a/src/main/java/io/tackle/applicationinventory/services/ImportService.java b/src/main/java/io/tackle/applicationinventory/services/ImportService.java new file mode 100644 index 0000000..b1815e4 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/services/ImportService.java @@ -0,0 +1,176 @@ +package io.tackle.applicationinventory.services; + +import com.fasterxml.jackson.databind.MappingIterator; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.dataformat.csv.CsvSchema; +import io.tackle.applicationinventory.BusinessService; +import io.tackle.applicationinventory.MultipartImportBody; +import io.tackle.applicationinventory.Tag; +import io.tackle.applicationinventory.entities.ApplicationImport; +import io.tackle.applicationinventory.entities.ImportSummary; +import io.tackle.applicationinventory.mapper.ApplicationInventoryAPIMapper; +import io.tackle.applicationinventory.mapper.ApplicationMapper; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; +import javax.transaction.UserTransaction; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.*; +import java.util.*; +import java.util.stream.Collectors; + +import static javax.transaction.Transactional.TxType.REQUIRED; + +@Path("/file") +@ApplicationScoped +public class ImportService { + + public static final String COMPLETED_STATUS = "Completed"; + public static final String IN_PROGRESS_STATUS = "In Progress"; + public static final String FAILED_STATUS = "Failed"; + + @Inject + EntityManager entityManager; + + @Inject + UserTransaction usrTransaction; + + @Inject + @RestClient + TagService tagService; + + @Inject + @RestClient + BusinessServiceService businessServiceService; + + @POST + @Path("/upload") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Transactional(REQUIRED) + public Response importFile(@MultipartForm MultipartImportBody data) { + ImportSummary parentRecord = new ImportSummary(); + + try { + parentRecord.filename = data.getFileName(); + parentRecord.importStatus = IN_PROGRESS_STATUS; + parentRecord.persistAndFlush(); + Set tags = tagService.getListOfTags(); + if (tags == null) + { + String msg = "Unable to retrieve TagTypes from remote resource"; + parentRecord.errorMessage = msg; + throw new Exception(msg); + } + Set businessServices =businessServiceService.getListOfBusinessServices(); + if (businessServices == null) + { + String msg = "Unable to retrieve BusinessServices from remote resource"; + parentRecord.errorMessage = msg; + throw new Exception(msg); + } + + List importList = writeFile(data.getFile(), data.getFileName(), parentRecord); + //we're not allowed duplicate application names within the file + Set discreteAppNames = new HashSet(); + //make a list of all the duplicate app names + List importListMinusDuplicates = importList; + List duplicateAppNames = importList.stream().filter(importApp -> + !discreteAppNames.add(importApp.getApplicationName())).collect(Collectors.toList()); + if( !duplicateAppNames.isEmpty()) + { + //find all the imported apps with a duplicate name and set appropriate error message + duplicateAppNames.forEach(app -> { + importList.stream().filter(importApp -> + app.getApplicationName().equals(importApp.getApplicationName())).collect(Collectors.toList()) + .forEach(duplicateApp -> { + importListMinusDuplicates.remove(duplicateApp); + duplicateApp.setErrorMessage("Duplicate Application Name within file: " + duplicateApp.getApplicationName()); + markFailedImportAsInvalid(duplicateApp); + }); + + }); + } + mapImportsToApplication(importListMinusDuplicates, tags, businessServices, parentRecord); + parentRecord.importStatus = COMPLETED_STATUS; + parentRecord.flush(); + + } catch (Exception e) { + + e.printStackTrace(); + parentRecord.importStatus = FAILED_STATUS; + parentRecord.flush(); + + } + return Response.ok().build(); + + + + } + + + private List writeFile(String content, String filename, ImportSummary parentObject) throws IOException { + + MappingIterator iter = decode(content); + List importList = new ArrayList(); + while (iter.hasNext()) + { + ApplicationImport importedApplication = iter.next(); + importedApplication.setFilename(filename); + importedApplication.importSummary = parentObject; + importList.add(importedApplication); + importedApplication.persistAndFlush(); + } + return importList; + } + + + + private MappingIterator decode(String inputFileContent) throws IOException{ + CsvMapper mapper = new CsvMapper(); + + CsvSchema csvSchema = CsvSchema.emptySchema().withHeader(); + String columnSeparator = ","; + + csvSchema = csvSchema.withColumnSeparator(columnSeparator.charAt(0)) + .withLineSeparator("\r\n") + .withUseHeader(true); + + ObjectReader reader = mapper.readerFor(ApplicationImport.class) + .with(csvSchema); + + return reader.readValues(inputFileContent); + + } + + public void mapImportsToApplication(List importList, Set tags, Set businessServices, ImportSummary parentRecord) + { + ApplicationMapper mapper = new ApplicationInventoryAPIMapper(tags, businessServices); + importList.forEach(importedApplication -> { + Response response = mapper.map(importedApplication, parentRecord.id); + if (response.getStatus() != Response.Status.OK.getStatusCode()) + { + markFailedImportAsInvalid(importedApplication); + } + + }); + } + + private void markFailedImportAsInvalid(ApplicationImport importFile) + { + importFile.setValid(Boolean.FALSE); + importFile.flush(); + } + + + + +} diff --git a/src/main/java/io/tackle/applicationinventory/services/TagService.java b/src/main/java/io/tackle/applicationinventory/services/TagService.java new file mode 100644 index 0000000..c91f3fd --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/services/TagService.java @@ -0,0 +1,23 @@ +package io.tackle.applicationinventory.services; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.GET; + +import io.tackle.applicationinventory.Tag; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import io.quarkus.oidc.token.propagation.AccessToken; + +import java.util.Set; + + + +@RegisterRestClient() +@AccessToken +@ApplicationScoped +public interface TagService { + + @GET + Set getListOfTags(); +} + + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 171c45c..59385b7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -81,3 +81,6 @@ quarkus.openshift.part-of=${quarkus.kubernetes.part-of} #quarkus.openshift.expose=${quarkus.kubernetes.expose} quarkus.kubernetes.labels."app.kubernetes.io/component"=rest quarkus.openshift.labels."app.kubernetes.io/component"=rest + +io.tackle.applicationinventory.services.TagService/mp-rest/uri=http://tackle-controls:8080/controls/tag?page=0&size=1000 +io.tackle.applicationinventory.services.BusinessServiceService/mp-rest/uri=http://tackle-controls:8080/controls/business-service?page=0&size=1000 diff --git a/src/main/resources/db/migration/V20210610.1__create_application_import.sql b/src/main/resources/db/migration/V20210610.1__create_application_import.sql new file mode 100644 index 0000000..7614f5b --- /dev/null +++ b/src/main/resources/db/migration/V20210610.1__create_application_import.sql @@ -0,0 +1,24 @@ +create table application_import ( + id int8 not null, + createTime timestamp, + createUser varchar(255), + deleted boolean, + updateTime timestamp, + updateUser varchar(255), + businessService varchar(255), + description varchar(255), + applicationname varchar(255), + comments varchar(255), + recordType1 varchar(255), + tag1 varchar(255), + tagType1 varchar(255), + tag2 varchar(255), + tagType2 varchar(255), + tag3 varchar(255), + tagType3 varchar(255), + tag4 varchar(255), + tagType4 varchar(255), + errorMessage varchar(255), + isValid boolean, + primary key (id) +) \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210610.2__alter_application_import.sql b/src/main/resources/db/migration/V20210610.2__alter_application_import.sql new file mode 100644 index 0000000..229b32c --- /dev/null +++ b/src/main/resources/db/migration/V20210610.2__alter_application_import.sql @@ -0,0 +1,2 @@ +alter table if exists application_import + add column filename varchar (255); \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210615.1__alter_application_import.sql b/src/main/resources/db/migration/V20210615.1__alter_application_import.sql new file mode 100644 index 0000000..a950535 --- /dev/null +++ b/src/main/resources/db/migration/V20210615.1__alter_application_import.sql @@ -0,0 +1,33 @@ +alter table if exists application_import + add column tagType5 varchar (255), + add column tag5 varchar (255), + add column tagType6 varchar (255), + add column tag6 varchar (255), + add column tagType7 varchar (255), + add column tag7 varchar (255), + add column tagType8 varchar (255), + add column tag8 varchar (255), + add column tagType9 varchar (255), + add column tag9 varchar (255), + add column tagType10 varchar (255), + add column tag10 varchar (255), + add column tagType11 varchar (255), + add column tag11 varchar (255), + add column tagType12 varchar (255), + add column tag12 varchar (255), + add column tagType13 varchar (255), + add column tag13 varchar (255), + add column tagType14 varchar (255), + add column tag14 varchar (255), + add column tagType15 varchar (255), + add column tag15 varchar (255), + add column tagType16 varchar (255), + add column tag16 varchar (255), + add column tagType17 varchar (255), + add column tag17 varchar (255), + add column tagType18 varchar (255), + add column tag18 varchar (255), + add column tagType19 varchar (255), + add column tag19 varchar (255), + add column tagType20 varchar (255), + add column tag20 varchar (255); \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210618.1__alter_application_import.sql b/src/main/resources/db/migration/V20210618.1__alter_application_import.sql new file mode 100644 index 0000000..2c8a087 --- /dev/null +++ b/src/main/resources/db/migration/V20210618.1__alter_application_import.sql @@ -0,0 +1,3 @@ +alter table if exists application_import + add column status varchar (255), + add column parentid int8; \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210621.1__alter_application_import.sql b/src/main/resources/db/migration/V20210621.1__alter_application_import.sql new file mode 100644 index 0000000..10a3eae --- /dev/null +++ b/src/main/resources/db/migration/V20210621.1__alter_application_import.sql @@ -0,0 +1,3 @@ +alter table if exists application_import + drop column parentid, + add column importSummary_id int8; \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210621.2__create_import_summary.sql b/src/main/resources/db/migration/V20210621.2__create_import_summary.sql new file mode 100644 index 0000000..ccc54e0 --- /dev/null +++ b/src/main/resources/db/migration/V20210621.2__create_import_summary.sql @@ -0,0 +1,12 @@ +create table import_summary ( + id int8 not null, + createTime timestamp, + createUser varchar(255), + deleted boolean, + updateTime timestamp, + updateUser varchar(255), + importStatus varchar(255), + filename varchar(255), + errorMessage varchar(255), + primary key (id) +) \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210621.3__alter_application_import.sql b/src/main/resources/db/migration/V20210621.3__alter_application_import.sql new file mode 100644 index 0000000..bbc0164 --- /dev/null +++ b/src/main/resources/db/migration/V20210621.3__alter_application_import.sql @@ -0,0 +1,6 @@ +alter table if exists application_import + drop column importSummary_id, + add column importSummary_id int8 not null, + add constraint FKp755h0bv2vgcmrsj1p4ebqjn6 + foreign key (importSummary_id) + references import_summary; \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210622.1__alter_import_summary.sql b/src/main/resources/db/migration/V20210622.1__alter_import_summary.sql new file mode 100644 index 0000000..e61514c --- /dev/null +++ b/src/main/resources/db/migration/V20210622.1__alter_import_summary.sql @@ -0,0 +1,2 @@ +alter table if exists import_summary + add column importTime time default current_time ; \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210622.2__alter_import_summary.sql b/src/main/resources/db/migration/V20210622.2__alter_import_summary.sql new file mode 100644 index 0000000..32c5cf3 --- /dev/null +++ b/src/main/resources/db/migration/V20210622.2__alter_import_summary.sql @@ -0,0 +1,3 @@ +alter table if exists import_summary + drop column importtime, + add column importTime timestamp default current_timestamp; \ No newline at end of file diff --git a/src/test/java/io/tackle/applicationimporter/ImportServiceTest.java b/src/test/java/io/tackle/applicationimporter/ImportServiceTest.java new file mode 100644 index 0000000..bb2b673 --- /dev/null +++ b/src/test/java/io/tackle/applicationimporter/ImportServiceTest.java @@ -0,0 +1,687 @@ +package io.tackle.applicationimporter; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.common.ResourceArg; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.restassured.RestAssured; +import io.restassured.config.EncoderConfig; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import io.tackle.applicationinventory.BusinessService; +import io.tackle.applicationinventory.MultipartImportBody; +import io.tackle.applicationinventory.Tag; +import io.tackle.applicationinventory.entities.Application; +import io.tackle.applicationinventory.entities.ApplicationImport; +import io.tackle.applicationinventory.entities.ImportSummary; +import io.tackle.applicationinventory.services.BusinessServiceService; +import io.tackle.applicationinventory.services.ImportService; +import io.tackle.applicationinventory.services.TagService; +import io.tackle.commons.testcontainers.KeycloakTestResource; +import io.tackle.commons.testcontainers.PostgreSQLDatabaseTestResource; +import io.tackle.commons.tests.SecuredResourceTest; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.*; +import org.mockito.Mockito; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.*; +import javax.ws.rs.core.MediaType; +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.containsInRelativeOrder; +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") + } +) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ImportServiceTest extends SecuredResourceTest { + @Inject + EntityManager entityManager; + + @Inject + UserTransaction userTransaction; + + @InjectMock + @RestClient + TagService mockTagService; + + @InjectMock + @RestClient + BusinessServiceService mockBusinessServiceService; + + @BeforeAll + public static void init() { + PATH = "/file/upload"; + } + + + @Test + @Order(1) + protected void testImportServicePost() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + + userTransaction.begin(); + Set tags = new HashSet<>() ; + Tag.TagType tagType1 = new Tag.TagType(); + tagType1.id = "1"; + tagType1.name = "Operating System"; + Tag tag = new Tag(); + tag.id = "1"; + tag.name = "RHEL 8"; + tag.tagType = tagType1; + tags.add(tag); + Tag.TagType tagType2 = new Tag.TagType(); + tagType2.id = "2"; + tagType2.name = "Database"; + Tag tag1 = new Tag(); + tag1.id = "2"; + tag1.name = "Oracle"; + tag1.tagType = tagType2; + tags.add(tag1); + Tag.TagType tagType3 = new Tag.TagType(); + tagType3.id = "3"; + tagType3.name = "Language"; + Tag tag2 = new Tag(); + tag2.id = "3"; + tag2.name = "Java EE"; + tag2.tagType = tagType3; + tags.add(tag2); + Tag.TagType tagType4 = new Tag.TagType(); + tagType4.id = "4"; + tagType4.name = "Runtime"; + Tag tag3 = new Tag(); + tag3.id = "3"; + tag3.name = "Tomcat"; + tag3.tagType = tagType4; + tags.add(tag3); + Mockito.when(mockTagService.getListOfTags()).thenReturn(tags); + + + Set businessServices = new HashSet<>() ; + BusinessService businessService = new BusinessService(); + businessService.id = "1"; + businessService.name = "Food2Go"; + businessServices.add(businessService); + Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(businessServices); + + ClassLoader classLoader = getClass().getClassLoader(); + File importFile = new File(classLoader.getResource("sample_application_import.csv").getFile()); + + + Response response = 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","sample_application_import.csv") + .when().post(PATH) + .then() + .log().all() + .statusCode(200).extract().response(); + + assertEquals(200, response.getStatusCode()); + //check the correct number of application imports have been persisted + assertEquals(7, ApplicationImport.listAll().size()); + userTransaction.commit(); + + given() + .accept("application/hal+json") + .queryParam("isValid", Boolean.TRUE) + .when() + .get("/application-import") + .then() + .statusCode(200) + .log().body() + .body("_embedded.'application-import'.size()", is(1)); + + userTransaction.begin(); + + Response response2 = 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","sample_application_import.csv") + .when().post(PATH) + .then() + .log().all() + .statusCode(200).extract().response(); + + assertEquals(200, response2.getStatusCode()); + //check the correct number of application imports have been persisted + assertEquals(14, ApplicationImport.listAll().size()); + userTransaction.commit(); + + given() + .accept("application/hal+json") + .queryParam("isValid", Boolean.TRUE) + .when() + .get("/application-import") + .then() + .statusCode(200) + .log().body() + .body("_embedded.'application-import'.size()", is(1)); + + ApplicationImport successful = ApplicationImport.find("isValid",true).firstResult(); + Application newOne = Application.find("name",successful.getApplicationName()).firstResult(); + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + Application.deleteById(newOne.id); + userTransaction.commit(); + + } + + @Test + @Order(2) + protected void testMapToApplicationRejected() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + userTransaction.begin(); + ImportService svc = new ImportService(); + ImportSummary appImportParent = new ImportSummary(); + appImportParent.persistAndFlush(); + + ApplicationImport appImport1 = new ApplicationImport(); + appImport1.setBusinessService("BS 1"); + appImport1.importSummary = appImportParent; + appImport1.setDescription("hello"); + appImport1.persistAndFlush(); + ApplicationImport appImport2 = new ApplicationImport(); + appImport2.setBusinessService("BS 2"); + appImport2.importSummary = appImportParent; + appImport2.setDescription("this"); + appImport2.setTag5("tag 1"); + appImport2.setTagType5("tag type 1"); + appImport2.setTag6("tag 1"); + appImport2.setTagType6("tag type 1"); + appImport2.setTag7("tag 1"); + appImport2.setTagType7("tag type 1"); + appImport2.setTag8("tag 1"); + appImport2.setTagType8("tag type 1"); + appImport2.setTag9("tag 1"); + appImport2.setTagType9("tag type 1"); + appImport2.setTag10("tag 1"); + appImport2.setTagType10("tag type 1"); + appImport2.setTag11("tag 1"); + appImport2.setTagType11("tag type 1"); + appImport2.setTag12("tag 1"); + appImport2.setTagType12("tag type 1"); + appImport2.setTag13("tag 1"); + appImport2.setTagType13("tag type 1"); + appImport2.setTag14("tag 1"); + appImport2.setTagType14("tag type 1"); + appImport2.setTag15("tag 1"); + appImport2.setTagType15("tag type 1"); + appImport2.setTag16("tag 1"); + appImport2.setTagType16("tag type 1"); + appImport2.setTag17("tag 1"); + appImport2.setTagType17("tag type 1"); + appImport2.setTag18("tag 1"); + appImport2.setTagType18("tag type 1"); + appImport2.setTag19("tag 1"); + appImport2.setTagType19("tag type 1"); + appImport2.setTag20("tag 1"); + appImport2.setTagType20("tag type 1"); + appImport2.persistAndFlush(); + ApplicationImport appImport3 = new ApplicationImport(); + appImport3.setBusinessService("BS 2"); + appImport3.importSummary = appImportParent; + appImport3.setDescription("and this"); + appImport3.setTag1(""); + appImport3.setTag2(""); + appImport3.setTagType2(""); + appImport3.setTag3("mystery tag"); + appImport3.setTagType3(""); + appImport3.setTag4(""); + appImport3.setTagType4(""); + appImport3.persistAndFlush(); + ApplicationImport appImport4 = new ApplicationImport(); + appImport4.setBusinessService("BS 2"); + appImport4.importSummary = appImportParent; + appImport4.setDescription("and this"); + appImport4.setTagType1(""); + appImport4.setTagType2("mystery tag type"); + appImport4.persistAndFlush(); + ApplicationImport appImport5 = new ApplicationImport(); + appImport5.setBusinessService("BS 2"); + appImport5.importSummary = appImportParent; + appImport5.setDescription("and this"); + appImport5.setTag1("yes"); + appImport5.persistAndFlush(); + + List appList = new ArrayList(); + + + appList.add(appImport1); + appList.add(appImport2); + appList.add(appImport3); + appList.add(appImport4); + appList.add(appImport5); + + + Long id1 = appImport1.id; + Long id2 = appImport2.id; + Long id3 = appImport3.id; + Long id4 = appImport4.id; + Long id5 = appImport5.id; + + Set tags = new HashSet<>() ; + Tag.TagType tagType1 = new Tag.TagType(); + tagType1.id = "1"; + tagType1.name = "Unknown tag type"; + Tag tag = new Tag(); + tag.id = "1"; + tag.name = "Unknown OS"; + tag.tagType = tagType1; + tags.add(tag); + + Set businessServices = new HashSet<>() ; + BusinessService businessService = new BusinessService(); + businessService.id = "1"; + businessService.name = "BS 2"; + businessServices.add(businessService); + svc.mapImportsToApplication(appList, tags, businessServices, appImportParent); + + + userTransaction.commit(); + + Long summaryId = appImportParent.id; + + + + ApplicationImport refusedImport = ApplicationImport.findById(id1); + assertEquals(Boolean.FALSE, refusedImport.getValid()); + assertEquals(summaryId, refusedImport.importSummary.id); + ApplicationImport refusedImport2 = ApplicationImport.findById(id2); + assertEquals(Boolean.FALSE, refusedImport2.getValid()); + assertEquals(summaryId, refusedImport2.importSummary.id); + ApplicationImport refusedImport3 = ApplicationImport.findById(id3); + assertEquals(Boolean.FALSE, refusedImport3.getValid()); + assertEquals(summaryId, refusedImport3.importSummary.id); + ApplicationImport refusedImport4 = ApplicationImport.findById(id4); + assertEquals(Boolean.FALSE, refusedImport4.getValid()); + assertEquals(summaryId, refusedImport4.importSummary.id); + ApplicationImport refusedImport5 = ApplicationImport.findById(id5); + assertEquals(Boolean.FALSE, refusedImport5.getValid()); + assertEquals(summaryId, refusedImport5.importSummary.id); + + given() + .accept("application/hal+json") + .when() + .get("/import-summary") + .then() + .statusCode(200) + .body("_embedded.import-summary.size()", is(1), + "_embedded.import-summary.invalidCount", containsInRelativeOrder(5), + "total_count", is(1)); + + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + userTransaction.commit(); + + } + + @Test + @Order(2) + protected void testMultipartImport() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + userTransaction.begin(); + ImportService svc = new ImportService(); + MultipartImportBody multipartImport = new MultipartImportBody(); + ClassLoader classLoader = getClass().getClassLoader(); + File importFile = new File(classLoader.getResource("sample_application_import.csv").getFile()); + multipartImport.setFilename("testImport"); + multipartImport.setFile(importFile.toString()); + + javax.ws.rs.core.Response response = svc.importFile(multipartImport); + assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(),response.getStatus()); + + + + + + userTransaction.commit(); + + + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + userTransaction.commit(); + + } + + @Test + @Order(2) + protected void testMapToApplicationMissingFields() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + userTransaction.begin(); + ImportService svc = new ImportService(); + + ImportSummary appImportParent = new ImportSummary(); + appImportParent.persistAndFlush(); + Long parentId = appImportParent.id; + + ApplicationImport appImport1 = new ApplicationImport(); + appImport1.setApplicationName("Test App 1"); + appImport1.importSummary = appImportParent; + appImport1.persistAndFlush(); + ApplicationImport appImport2 = new ApplicationImport(); + appImport2.setApplicationName("Test App 2"); + appImport2.importSummary = appImportParent; + appImport2.setBusinessService(("")); + appImport2.persistAndFlush(); + ApplicationImport appImport3= new ApplicationImport(); + appImport3.setApplicationName("Test App 3"); + appImport3.importSummary = appImportParent; + appImport3.setBusinessService(("BS 2")); + appImport3.persistAndFlush(); + ApplicationImport appImport4= new ApplicationImport(); + appImport4.setApplicationName("Test App 4"); + appImport4.importSummary = appImportParent; + appImport4.setBusinessService(("BS 2")); + appImport4.setDescription(""); + appImport4.persistAndFlush(); + + + List appList = new ArrayList(); + + + appList.add(appImport1); + appList.add(appImport2); + appList.add(appImport3); + appList.add(appImport4); + + + Long id = appImport1.id; + Long id2 = appImport2.id; + Long id3 = appImport3.id; + Long id4 = appImport4.id; + + Set tags = new HashSet<>(); + Set businessServices = new HashSet<>() ; + BusinessService businessService = new BusinessService(); + businessService.id = "1"; + businessService.name = "BS 2"; + businessServices.add(businessService); + svc.mapImportsToApplication(appList, tags, businessServices, appImportParent); + + + userTransaction.commit(); + + ApplicationImport refusedImport1 = ApplicationImport.findById(id); + assertEquals(Boolean.FALSE, refusedImport1.getValid()); + assertEquals("Business Service is Mandatory",refusedImport1.getErrorMessage()); + + ApplicationImport refusedImport2 = ApplicationImport.findById(id2); + assertEquals(Boolean.FALSE, refusedImport2.getValid()); + assertEquals("Business Service is Mandatory",refusedImport2.getErrorMessage()); + + ApplicationImport refusedImport3 = ApplicationImport.findById(id3); + assertEquals(Boolean.FALSE, refusedImport3.getValid()); + assertEquals("Description is Mandatory",refusedImport3.getErrorMessage()); + + ApplicationImport refusedImport4 = ApplicationImport.findById(id4); + assertEquals(Boolean.FALSE, refusedImport4.getValid()); + assertEquals("Description is Mandatory",refusedImport4.getErrorMessage()); + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + userTransaction.commit(); + + } + + @Test + @Order(3) + protected void testImportServiceNoMatchingTag() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + + + Set tags = new HashSet<>() ; + Tag.TagType tagType1 = new Tag.TagType(); + tagType1.id = "1"; + tagType1.name = "Unknown tag type"; + Tag tag = new Tag(); + tag.id = "1"; + tag.name = "Unknown OS"; + tag.tagType = tagType1; + tags.add(tag); + Mockito.when(mockTagService.getListOfTags()).thenReturn(tags); + + + Set businessServices = new HashSet<>() ; + BusinessService businessService = new BusinessService(); + businessService.id = "1"; + businessService.name = "Foot2Go"; + businessServices.add(businessService); + + BusinessService businessService2 = new BusinessService(); + businessService2.id = "2"; + businessService2.name = "Food2Go"; + businessServices.add(businessService2); + Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(businessServices); + + ClassLoader classLoader = getClass().getClassLoader(); + File importFile = new File(classLoader.getResource("sample_application_import.csv").getFile()); + + + Response response = 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","sample_application_import.csv") + .when().post(PATH) + .then() + .log().all() + .statusCode(200).extract().response(); + + assertEquals(200, response.getStatusCode()); + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + userTransaction.commit(); + } + + @Test + @Order(4) + protected void testImportServiceDuplicatesInFile() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + + Set tags = new HashSet<>() ; + Tag.TagType tagType1 = new Tag.TagType(); + tagType1.id = "1"; + tagType1.name = "Operating System"; + Tag tag = new Tag(); + tag.id = "1"; + tag.name = "RHEL"; + tag.tagType = tagType1; + tags.add(tag); + Mockito.when(mockTagService.getListOfTags()).thenReturn(tags); + + + Set businessServices = new HashSet<>() ; + BusinessService businessService = new BusinessService(); + businessService.id = "1"; + businessService.name = "Food2Go"; + businessServices.add(businessService); + Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(businessServices); + + ClassLoader classLoader = getClass().getClassLoader(); + File importFile = new File(classLoader.getResource("duplicate_application_names.csv").getFile()); + + + Response response = 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","duplicate_application_names.csv") + .when().post(PATH) + .then() + .log().all() + .statusCode(200).extract().response(); + + assertEquals(200, response.getStatusCode()); + + given() + .accept("application/hal+json") + .queryParam("isValid", Boolean.FALSE) + .when() + .get("/application-import") + .then() + .statusCode(200) + .log().body() + .body("_embedded.'application-import'[0].'errorMessage'", is("Duplicate Application Name within file: OrderHub")); + + given() + .accept("application/hal+json") + .when() + .get("/import-summary") + .then() + .statusCode(200) + .log().body() + .body("_embedded.'import-summary'[0].'importStatus'", is("Completed")); + + given() + .accept("application/json") + .when() + .get("/import-summary") + .then() + .statusCode(200) + .log().body() + .body("size()", is(1)); + + ImportSummary summary = ImportSummary.findAll().firstResult(); + + Response r = + given() + .accept("text/csv") + .when() + .get("/csv-export?importSummaryId=" + summary.id); + /** .then() + .statusCode(200) + .log().body(); + .body("", is("Completed"));*/ + + + String csv = r.body().print(); + String[] csvFields = csv.split(","); + List found = Arrays.stream(csvFields).filter("Comments"::equals).collect(Collectors.toList()); + assertEquals(1,found.size()); + + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + userTransaction.commit(); + + } + + @Test + @Order(5) + protected void testImportServiceNoTagsRetrieved() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + + + Mockito.when(mockTagService.getListOfTags()).thenReturn(null); + + + Set businessServices = new HashSet<>() ; + BusinessService businessService = new BusinessService(); + businessService.id = "1"; + businessService.name = "Food2Go"; + businessServices.add(businessService); + Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(businessServices); + + ClassLoader classLoader = getClass().getClassLoader(); + File importFile = new File(classLoader.getResource("duplicate_application_names.csv").getFile()); + + + Response response = 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","sample_application_import.csv") + .when().post(PATH) + .then() + .log().all() + .statusCode(200).extract().response(); + + assertEquals(200, response.getStatusCode()); + + + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + userTransaction.commit(); + + } + + @Test + @Order(5) + protected void testImportServiceNoBSRetrieved() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + + + Mockito.when(mockTagService.getListOfTags()).thenReturn(null); + + + Set tags = new HashSet<>() ; + Tag.TagType tagType1 = new Tag.TagType(); + tagType1.id = "1"; + tagType1.name = "Operating System"; + Tag tag = new Tag(); + tag.id = "1"; + tag.name = "RHEL"; + tag.tagType = tagType1; + tags.add(tag); + Mockito.when(mockTagService.getListOfTags()).thenReturn(tags); + Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(null); + + ClassLoader classLoader = getClass().getClassLoader(); + File importFile = new File(classLoader.getResource("duplicate_application_names.csv").getFile()); + + + Response response = 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","sample_application_import.csv") + .when().post(PATH) + .then() + .log().all() + .statusCode(200).extract().response(); + + assertEquals(200, response.getStatusCode()); + + + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + userTransaction.commit(); + + } + + +} + diff --git a/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java b/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java index b4dccb0..0aa31a0 100644 --- a/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java +++ b/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java @@ -20,9 +20,9 @@ public class FlywayMigrationTest { @Test public void testMigration() { // check the number of migrations applied equals the number of files in resources/db/migration folder - assertEquals(7, flyway.info().applied().length); + assertEquals(16, flyway.info().applied().length); // check the current migration version is the one from the last file in resources/db/migration folder - assertEquals("20210603", flyway.info().current().getVersion().toString()); + assertEquals("20210622.2", flyway.info().current().getVersion().toString()); // just a basic test to double check the application started // to prove the flyway scripts ran successfully during startup given() diff --git a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportTest.java b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportTest.java new file mode 100644 index 0000000..cfcb25d --- /dev/null +++ b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportTest.java @@ -0,0 +1,109 @@ +package io.tackle.applicationinventory.resources; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.common.ResourceArg; +import io.quarkus.test.junit.QuarkusTest; +import io.tackle.applicationinventory.entities.ApplicationImport; +import io.tackle.applicationinventory.entities.ImportSummary; +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.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.*; + +import static io.restassured.RestAssured.given; +import static javax.transaction.Transactional.TxType.REQUIRED; +import static org.hamcrest.Matchers.is; + +@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") + } +) +public class ApplicationImportTest extends SecuredResourceTest { + @Inject + EntityManager entityManager; + + @Inject + UserTransaction userTransaction; + + + @BeforeAll + public static void init() { + + PATH = "/application-import"; + + } + + @Test + public void testFilterByIsValid() throws HeuristicRollbackException, SystemException, HeuristicMixedException, RollbackException, NotSupportedException { + + userTransaction.begin(); + + ImportSummary appImportParent = new ImportSummary(); + appImportParent.persistAndFlush(); + + ApplicationImport appImport1 = new ApplicationImport(); + appImport1.setBusinessService("BS 1"); + appImport1.importSummary = appImportParent; + appImport1.setFilename("File1"); + appImport1.persistAndFlush(); + ApplicationImport appImport2 = new ApplicationImport(); + appImport2.setBusinessService("BS 2"); + appImport2.importSummary = appImportParent; + appImport2.setFilename("File1"); + appImport2.setTag1("tag 1"); + appImport2.setTagType1("tag type 1"); + appImport2.setValid(Boolean.FALSE); + appImport2.persistAndFlush(); + ApplicationImport appImport3 = new ApplicationImport(); + appImport3.setBusinessService("BS 3"); + appImport3.importSummary = appImportParent; + appImport3.setFilename("File2"); + appImport3.setValid(Boolean.FALSE); + appImport3.persistAndFlush(); + + userTransaction.commit(); + + given() + .accept("application/hal+json") + .queryParam("isValid", Boolean.FALSE) + .queryParam("filename","File1") + .when() + .get(PATH) + .then() + .statusCode(200) + .log().body() + .body("_embedded.'application-import'.size()", is(1)) + .body("_embedded.'application-import'[0].'Business Service'", is("BS 2")) + .body("_embedded.'application-import'[0].'Tag Type 1'", is("tag type 1")); + + given() + .accept("application/json") + .queryParam("isValid", Boolean.FALSE) + .when() + .get(PATH) + .then() + .statusCode(200) + .log().body() + .body("size()", is(2)); + + userTransaction.begin(); + ApplicationImport.deleteAll(); + ImportSummary.deleteAll(); + userTransaction.commit(); + } +} diff --git a/src/test/resources/duplicate_application_names.csv b/src/test/resources/duplicate_application_names.csv new file mode 100644 index 0000000..0d58aab --- /dev/null +++ b/src/test/resources/duplicate_application_names.csv @@ -0,0 +1,8 @@ +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 +1,OrderHub,"Create, amend and cancel food orders",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat +1,MenuManager,Food vendors maintain their menu and price data which is curated and presented to customers via Orderhub,,Food2Go,Operating System,Windows Server 2016,Database,SQL Server,Language,C# ASP .Net,, +1,HeadChef,Allocation of orders to restaurants for fulfillment then despatch,,Foot2Go,Operating System,RHEL 8,Database,Postgresql,Language,Java EE,Runtime,Spring Boot +1,OrderHub,Delivery service,,Foot2Go,Operating System,RHEL 8,Database,Postgresql,Language,Python,, +1,PocketMoney,Payment service,Incoming from orders and outgoing to delivery drivers,Payment Management,Operating System,Z/OS,Database,DB2,Language,COBOL,, +1,BeanCounter,Accounting Service,COTS ERP system,Accounting,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Application Type,COTS +1,SmokeSignal,Notification Service,Updates customers regarding the progress of their orders. Allocates deliveries to despatch drivers,Foot2Go,Operating System,RHEL 8,Language,Python,DataCentre,London (UK),, \ No newline at end of file diff --git a/src/test/resources/sample_application_import.csv b/src/test/resources/sample_application_import.csv new file mode 100644 index 0000000..a7aabb7 --- /dev/null +++ b/src/test/resources/sample_application_import.csv @@ -0,0 +1,8 @@ +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 +1,OrderHub,"Create, amend and cancel food orders",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat +1,MenuManager,Food vendors maintain their menu and price data which is curated and presented to customers via Orderhub,,Food2Go,Operating System,Windows Server 2016,Database,SQL Server,Language,C# ASP .Net,, +1,HeadChef,Allocation of orders to restaurants for fulfillment then despatch,,Foot2Go,Operating System,RHEL 8,Database,Postgresql,Language,Java EE,Runtime,Spring Boot +1,Despatcher,Delivery service,,Foot2Go,Operating System,RHEL 8,Database,Postgresql,Language,Python,, +1,PocketMoney,Payment service,Incoming from orders and outgoing to delivery drivers,Payment Management,Operating System,Z/OS,Database,DB2,Language,COBOL,, +1,BeanCounter,Accounting Service,COTS ERP system,Accounting,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Application Type,COTS +1,SmokeSignal,Notification Service,Updates customers regarding the progress of their orders. Allocates deliveries to despatch drivers,Foot2Go,Operating System,RHEL 8,Language,Python,DataCentre,London (UK),, \ No newline at end of file