diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/errorhandling/DatabaseItemNotFoundException.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/errorhandling/DatabaseItemNotFoundException.java new file mode 100644 index 000000000..8fa6a74b8 --- /dev/null +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/errorhandling/DatabaseItemNotFoundException.java @@ -0,0 +1,41 @@ +package org.opendcs.odcsapi.errorhandling; + +import javax.servlet.http.HttpServletResponse; + +public class DatabaseItemNotFoundException extends WebAppException +{ + /** detailed error msg */ + private String errMessage = ""; + + public DatabaseItemNotFoundException(String errMessage) + { + super(HttpServletResponse.SC_NOT_FOUND, errMessage); + this.errMessage = errMessage; + } + + public DatabaseItemNotFoundException(String errMessage, Throwable throwable) + { + super(HttpServletResponse.SC_NOT_FOUND, errMessage, throwable); + this.errMessage = errMessage; + } + + public DatabaseItemNotFoundException() { } + + @Override + public int getStatus() + { + return HttpServletResponse.SC_NOT_FOUND; + } + + @Override + public String getErrMessage() + { + return errMessage; + } + + @Override + public void setErrMessage(String errMessage) + { + this.errMessage = errMessage; + } +} \ No newline at end of file diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/errorhandling/MissingParameterException.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/errorhandling/MissingParameterException.java new file mode 100644 index 000000000..1bc208a98 --- /dev/null +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/errorhandling/MissingParameterException.java @@ -0,0 +1,41 @@ +package org.opendcs.odcsapi.errorhandling; + +import javax.servlet.http.HttpServletResponse; + +public class MissingParameterException extends WebAppException +{ + /** detailed error msg */ + private String errMessage = ""; + + public MissingParameterException(String errMessage) + { + super(HttpServletResponse.SC_BAD_REQUEST, errMessage); + this.errMessage = errMessage; + } + + public MissingParameterException(String errMessage, Throwable throwable) + { + super(HttpServletResponse.SC_BAD_REQUEST, errMessage, throwable); + this.errMessage = errMessage; + } + + public MissingParameterException() { } + + @Override + public int getStatus() + { + return HttpServletResponse.SC_BAD_REQUEST; + } + + @Override + public String getErrMessage() + { + return errMessage; + } + + @Override + public void setErrMessage(String errMessage) + { + this.errMessage = errMessage; + } +} \ No newline at end of file diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/PresentationResources.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/PresentationResources.java index 8edaa70d1..b473497b4 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/PresentationResources.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/PresentationResources.java @@ -15,9 +15,12 @@ package org.opendcs.odcsapi.res; -import java.sql.SQLException; -import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + import javax.annotation.security.RolesAllowed; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -30,17 +33,29 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import decodes.db.DataPresentation; +import decodes.db.DataType; +import decodes.db.DatabaseException; +import decodes.db.DatabaseIO; +import decodes.db.PresentationGroup; +import decodes.db.PresentationGroupList; +import decodes.db.RoutingSpec; +import decodes.db.ValueNotFoundException; +import decodes.sql.DbKey; +import decodes.tsdb.DbIoException; +import decodes.tsdb.NoSuchObjectException; +import opendcs.dai.DataTypeDAI; +import org.opendcs.odcsapi.beans.ApiPresentationElement; import org.opendcs.odcsapi.beans.ApiPresentationGroup; -import org.opendcs.odcsapi.dao.ApiPresentationDAO; +import org.opendcs.odcsapi.beans.ApiPresentationRef; import org.opendcs.odcsapi.dao.DbException; -import org.opendcs.odcsapi.errorhandling.ErrorCodes; +import org.opendcs.odcsapi.errorhandling.DatabaseItemNotFoundException; +import org.opendcs.odcsapi.errorhandling.MissingParameterException; import org.opendcs.odcsapi.errorhandling.WebAppException; -import org.opendcs.odcsapi.hydrojson.DbInterface; import org.opendcs.odcsapi.util.ApiConstants; -import org.opendcs.odcsapi.util.ApiHttpUtil; @Path("/") -public class PresentationResources +public class PresentationResources extends OpenDcsResource { @Context HttpHeaders httpHeaders; @@ -50,12 +65,59 @@ public class PresentationResources @RolesAllowed({ApiConstants.ODCS_API_GUEST}) public Response getPresentationRefs() throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("getPresentationRefs"); - try (DbInterface dbi = new DbInterface(); - ApiPresentationDAO dao = new ApiPresentationDAO(dbi)) + DatabaseIO dbIo = getLegacyDatabase(); + try + { + PresentationGroupList groupList = new PresentationGroupList(); + dbIo.readPresentationGroupList(groupList); + return Response.status(HttpServletResponse.SC_OK).entity(map(groupList)).build(); + } + catch (DatabaseException e) + { + throw new DbException("Unable to retrieve presentation groups", e); + } + finally + { + dbIo.close(); + } + } + + static ArrayList map(PresentationGroupList groupList) + { + ArrayList ret = new ArrayList<>(); + for (PresentationGroup group : groupList.getVector()) { - return ApiHttpUtil.createResponse(dao.getPresentationRefs()); + ApiPresentationRef presRef = new ApiPresentationRef(); + if (group.getId() != null) + { + presRef.setGroupId(group.getId().getValue()); + } + else + { + presRef.setGroupId(DbKey.NullKey.getValue()); + } + presRef.setName(group.groupName); + presRef.setInheritsFrom(group.inheritsFrom); + presRef.setProduction(group.isProduction); + if (group.inheritsFrom != null && !group.inheritsFrom.isEmpty()) + { + for (PresentationGroup pg : groupList.getVector()) + { + if (pg.groupName.equalsIgnoreCase(group.inheritsFrom)) + { + presRef.setInheritsFromId(pg.getId().getValue()); + break; + } + } + } + else + { + presRef.setInheritsFromId(DbKey.NullKey.getValue()); + } + presRef.setLastModified(group.lastModifyTime); + ret.add(presRef); } + return ret; } @GET @@ -63,18 +125,73 @@ public Response getPresentationRefs() throws DbException @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ApiConstants.ODCS_API_GUEST}) public Response getPresentation(@QueryParam("groupid") Long groupId) - throws WebAppException, DbException, SQLException + throws WebAppException, DbException { if (groupId == null) - throw new WebAppException(ErrorCodes.MISSING_ID, - "Missing required groupid parameter."); - - Logger.getLogger(ApiConstants.loggerName).fine("getPresentation id=" + groupId); - try (DbInterface dbi = new DbInterface(); - ApiPresentationDAO dao = new ApiPresentationDAO(dbi)) { - return ApiHttpUtil.createResponse(dao.getPresentation(groupId)); + throw new MissingParameterException("Missing required groupid parameter."); + } + + DatabaseIO dbIo = getLegacyDatabase(); + try + { + PresentationGroup group = new PresentationGroup(); + group.setId(DbKey.createDbKey(groupId)); + dbIo.readPresentationGroup(group); + return Response.status(HttpServletResponse.SC_OK).entity(map(group)).build(); + } + catch (DatabaseException e) + { + if (e.getCause() instanceof ValueNotFoundException) + { + throw new DatabaseItemNotFoundException(String.format("Presentation group with ID %s not found", groupId)); + } + throw new DbException(String.format("Unable to retrieve presentation group with ID: %s", groupId), e); + } + finally + { + dbIo.close(); + } + } + + static ApiPresentationGroup map(PresentationGroup group) + { + ApiPresentationGroup presGrp = new ApiPresentationGroup(); + presGrp.setLastModified(group.lastModifyTime); + presGrp.setName(group.groupName); + presGrp.setProduction(group.isProduction); + if (group.parent != null && group.parent.groupName != null && !group.parent.groupName.isEmpty()) + { + presGrp.setInheritsFrom(group.parent.groupName); + presGrp.setInheritsFromId(group.parent.getId().getValue()); + } + if (group.getId() != null) + { + presGrp.setGroupId(group.getId().getValue()); } + else + { + presGrp.setGroupId(DbKey.NullKey.getValue()); + } + presGrp.setElements(map(group.dataPresentations)); + return presGrp; + } + + static List map(List dataPresentations) + { + List ret = new ArrayList<>(); + for(DataPresentation dp : dataPresentations) + { + ApiPresentationElement ape = new ApiPresentationElement(); + ape.setDataTypeCode(dp.getDataType().getCode()); + ape.setDataTypeStd(dp.getDataType().getStandard()); + ape.setFractionalDigits(dp.getMaxDecimals()); + ape.setMax(dp.getMaxValue()); + ape.setMin(dp.getMinValue()); + ape.setUnits(dp.getUnitsAbbr()); + ret.add(ape); + } + return ret; } @POST @@ -82,18 +199,100 @@ public Response getPresentation(@QueryParam("groupid") Long groupId) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ApiConstants.ODCS_API_ADMIN, ApiConstants.ODCS_API_USER}) - public Response postPresentation(ApiPresentationGroup presGrp) throws WebAppException, DbException, SQLException + public Response postPresentation(ApiPresentationGroup presGrp) throws DbException { - Logger.getLogger(ApiConstants.loggerName) - .fine("post presentation received presentation " + presGrp.getName() - + " with ID=" + presGrp.getGroupId()); + DatabaseIO dbIo = getLegacyDatabase(); + try (DataTypeDAI dai = getLegacyTimeseriesDB().makeDataTypeDAO()) + { + PresentationGroup group = map(dai, presGrp); + dbIo.writePresentationGroup(group); + return Response.status(HttpServletResponse.SC_CREATED) + .entity(map(group)) + .build(); + } + catch (DatabaseException e) + { + throw new DbException("Unable to store presentation group", e); + } + finally + { + dbIo.close(); + } + } - try (DbInterface dbi = new DbInterface(); - ApiPresentationDAO dao = new ApiPresentationDAO(dbi)) + static PresentationGroup map(DataTypeDAI dai, ApiPresentationGroup presGrp) throws DatabaseException + { + // The inheritsFromId is not present in the target data object, so it is not mapped + // The inheritsFromId is found internally using the inheritsFrom group name + PresentationGroup group = new PresentationGroup(); + group.lastModifyTime = presGrp.getLastModified(); + group.groupName = presGrp.getName(); + if (presGrp.getGroupId() != null) + { + group.setId(DbKey.createDbKey(presGrp.getGroupId())); + } + else + { + group.setId(DbKey.NullKey); + } + group.isProduction = presGrp.isProduction(); + group.inheritsFrom = presGrp.getInheritsFrom(); + PresentationGroup apiGroup = new PresentationGroup(); + apiGroup.groupName = presGrp.getInheritsFrom(); + if (presGrp.getInheritsFromId() != null) + { + apiGroup.setId(DbKey.createDbKey(presGrp.getInheritsFromId())); + } + else { - dao.writePresentation(presGrp); - return ApiHttpUtil.createResponse(presGrp); + apiGroup.setId(DbKey.NullKey); } + group.parent = apiGroup; + group.dataPresentations = map(dai, presGrp.getElements(), group); + + return group; + } + + static Vector map(DataTypeDAI dai, List elements, PresentationGroup group) + throws DatabaseException + { + Vector ret = new Vector<>(); + + for (ApiPresentationElement ape : elements) + { + DataPresentation dataPres = new DataPresentation(); + dataPres.setUnitsAbbr(ape.getUnits()); + DataType dt = new DataType(ape.getDataTypeStd(), ape.getDataTypeCode()); + + // Perform a lookup to see if the data type exists in the database + // If it does, use the existing data type, otherwise create a new one by setting the ID to null + if (dai != null) + { + try + { + DataType retDt = dai.lookupDataType(ape.getDataTypeCode()); + if(retDt != null) + { + dt = retDt; + } + } + catch (DbIoException | NoSuchObjectException e) + { + dt.setId(DbKey.NullKey); + } + } + else + { + dt.setId(DbKey.NullKey); + } + dataPres.setDataType(dt); + dataPres.setMaxDecimals(ape.getFractionalDigits()); + dataPres.setMinValue(ape.getMin()); + dataPres.setMaxValue(ape.getMax()); + dataPres.setGroup(group); + ret.add(dataPres); + } + return ret; } @DELETE @@ -101,23 +300,48 @@ public Response postPresentation(ApiPresentationGroup presGrp) throws WebAppExce @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ApiConstants.ODCS_API_ADMIN, ApiConstants.ODCS_API_USER}) - public Response deletePresentation(@QueryParam("groupid") Long groupId) throws DbException, SQLException + public Response deletePresentation(@QueryParam("groupid") Long groupId) throws DbException, WebAppException { - Logger.getLogger(ApiConstants.loggerName) - .fine("DELETE presentation received groupid=" + groupId); - - // Use username and password to attempt to connect to the database - try (DbInterface dbi = new DbInterface(); - ApiPresentationDAO dao = new ApiPresentationDAO(dbi)) - { - String s = dao.routSpecsUsing(groupId); - if (s != null) - return ApiHttpUtil.createResponse("Cannot delete presentation group " + groupId - + " because it is used by the following routing specs: " - + s, ErrorCodes.NOT_ALLOWED); - - dao.deletePresentation(groupId); - return ApiHttpUtil.createResponse("Presentation Group with ID " + groupId + " deleted"); + if (groupId == null) + { + throw new MissingParameterException("Missing required groupid parameter."); + } + + DatabaseIO dbIo = getLegacyDatabase(); + try + { + PresentationGroup group = new PresentationGroup(); + group.setId(DbKey.createDbKey(groupId)); + + List routeList = dbIo.routeSpecsUsing(groupId); + if (!routeList.isEmpty()) + { + StringBuilder sb = new StringBuilder(); + for (RoutingSpec rs : routeList) + { + sb.append(String.format("%s:%s, ", rs.getId(), rs.getName())); + } + String routeSpecs = sb.toString(); + if (routeSpecs.endsWith(", ")) + { + routeSpecs = routeSpecs.substring(0, routeSpecs.length() - 2); + } + return Response.status(HttpServletResponse.SC_METHOD_NOT_ALLOWED) + .entity(String.format("Cannot delete presentation group %s " + + "because it is used by the following routing specs: %s", groupId, routeSpecs)).build(); + } + dbIo.deletePresentationGroup(group); + return Response.status(HttpServletResponse.SC_NO_CONTENT) + .entity("Presentation Group with ID " + groupId + " deleted") + .build(); + } + catch (DatabaseException e) + { + throw new DbException("Unable to delete presentation group", e); + } + finally + { + dbIo.close(); } } -} +} \ No newline at end of file diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/PresentationResourcesTest.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/PresentationResourcesTest.java new file mode 100644 index 000000000..9f71c1a5c --- /dev/null +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/PresentationResourcesTest.java @@ -0,0 +1,192 @@ +package org.opendcs.odcsapi.res; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Vector; + +import decodes.db.DataPresentation; +import decodes.db.DataType; +import decodes.db.PresentationGroup; +import decodes.db.PresentationGroupList; +import decodes.sql.DbKey; +import org.junit.jupiter.api.Test; +import org.opendcs.odcsapi.beans.ApiPresentationElement; +import org.opendcs.odcsapi.beans.ApiPresentationGroup; +import org.opendcs.odcsapi.beans.ApiPresentationRef; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.opendcs.odcsapi.res.PresentationResources.map; + +final class PresentationResourcesTest +{ + @Test + void testPresentationGroupListMap() throws Exception + { + PresentationGroupList pgl = new PresentationGroupList(); + PresentationGroup pg = new PresentationGroup(); + pg.setId(DbKey.createDbKey(1234L)); + pg.inheritsFrom = "Parent Presentation Group"; + pg.groupName = "Presentation Group"; + pg.lastModifyTime = Date.from(Instant.parse("2021-07-01T00:00:00Z")); + pg.isProduction = true; + DataPresentation dataPres = new DataPresentation(); + dataPres.setDataType(new DataType("New Data Type", "TST")); + dataPres.setId(DbKey.createDbKey(1234567L)); + dataPres.setMaxDecimals(2); + dataPres.setMaxValue(100.0); + dataPres.setMinValue(0.0); + dataPres.setUnitsAbbr("TST"); + pg.addDataPresentation(dataPres); + pgl.add(pg); + + ArrayList presentationRefs = map(pgl); + assertNotNull(presentationRefs); + assertEquals(1, presentationRefs.size()); + ApiPresentationRef apiPresentationRef = presentationRefs.get(0); + assertEquals(pg.inheritsFrom, apiPresentationRef.getInheritsFrom()); + assertEquals(pg.groupName, apiPresentationRef.getName()); + assertEquals(pg.isProduction, apiPresentationRef.isProduction()); + assertEquals(pg.getId().getValue(), apiPresentationRef.getGroupId()); + assertEquals(pg.lastModifyTime, apiPresentationRef.getLastModified()); + } + + @Test + void testPresentationGroupMap() throws Exception + { + PresentationGroup pg = new PresentationGroup(); + pg.groupName = "Presentation Group"; + pg.inheritsFrom = "Parent Presentation Group"; + pg.isProduction = true; + PresentationGroup parentGroup = new PresentationGroup(); + parentGroup.groupName = "Parent Presentation Group"; + parentGroup.setId(DbKey.createDbKey(99895L)); + pg.parent = parentGroup; + pg.lastModifyTime = Date.from(Instant.parse("2021-07-01T00:00:00Z")); + DataPresentation dataPres = new DataPresentation(); + dataPres.setDataType(new DataType("New Data Type", "TST")); + dataPres.setId(DbKey.createDbKey(1234567L)); + dataPres.setMaxDecimals(2); + dataPres.setMaxValue(100.0); + dataPres.setMinValue(0.0); + dataPres.setUnitsAbbr("TST"); + pg.addDataPresentation(dataPres); + + ApiPresentationGroup apiPresentationGroup = map(pg); + assertNotNull(apiPresentationGroup); + assertEquals(pg.inheritsFrom, apiPresentationGroup.getInheritsFrom()); + assertEquals(pg.groupName, apiPresentationGroup.getName()); + assertEquals(pg.isProduction, apiPresentationGroup.isProduction()); + assertEquals(pg.getId().getValue(), apiPresentationGroup.getGroupId()); + assertEquals(pg.lastModifyTime, apiPresentationGroup.getLastModified()); + assertEquals(pg.getId().getValue(), apiPresentationGroup.getGroupId()); + } + + @Test + void testPresentationElementMap() throws Exception + { + PresentationGroup pg = new PresentationGroup(); + pg.setId(DbKey.createDbKey(1234L)); + pg.inheritsFrom = "Parent Presentation Group"; + pg.groupName = "Presentation Group"; + pg.lastModifyTime = Date.from(Instant.parse("2021-07-01T00:00:00Z")); + List elementList = new ArrayList<>(); + ApiPresentationElement ape = new ApiPresentationElement(); + ape.setDataTypeCode("TST"); + ape.setMax(100.0); + ape.setMin(0.0); + ape.setUnits("TST"); + ape.setDataTypeStd("String"); + ape.setFractionalDigits(2); + elementList.add(ape); + + Vector dataPresentations = map(null, elementList, pg); + assertNotNull(dataPresentations); + assertEquals(1, dataPresentations.size()); + DataPresentation dataPres = dataPresentations.get(0); + assertEquals(ape.getDataTypeCode(), dataPres.getDataType().getCode()); + assertEquals(ape.getUnits(), dataPres.getUnitsAbbr()); + assertEquals(ape.getFractionalDigits(), dataPres.getMaxDecimals()); + assertEquals(ape.getMax(), dataPres.getMaxValue()); + assertEquals(ape.getMin(), dataPres.getMinValue()); + assertEquals(pg.groupName, dataPres.getGroup().groupName); + assertEquals(pg.getId().getValue(), dataPres.getGroup().getId().getValue()); + assertEquals(pg.inheritsFrom, dataPres.getGroup().inheritsFrom); + } + + @Test + void testApiPresentationGroupMap() throws Exception + { + ApiPresentationGroup apiPresentationGroup = new ApiPresentationGroup(); + apiPresentationGroup.setGroupId(1234L); + apiPresentationGroup.setInheritsFrom("Parent Presentation Group"); + apiPresentationGroup.setName("Presentation Group"); + apiPresentationGroup.setProduction(true); + apiPresentationGroup.setLastModified(Date.from(Instant.parse("2021-07-01T00:00:00Z"))); + ArrayList elements = new ArrayList<>(); + ApiPresentationElement ape = new ApiPresentationElement(); + ape.setDataTypeCode("TST"); + ape.setMax(100.0); + ape.setMin(0.0); + ape.setUnits("TST"); + ape.setDataTypeStd("String"); + ape.setFractionalDigits(2); + elements.add(ape); + apiPresentationGroup.setElements(elements); + + PresentationGroup pg = map(null, apiPresentationGroup); + + assertNotNull(pg); + assertEquals(apiPresentationGroup.getInheritsFrom(), pg.inheritsFrom); + assertEquals(apiPresentationGroup.getName(), pg.groupName); + assertEquals(apiPresentationGroup.isProduction(), pg.isProduction); + assertEquals(apiPresentationGroup.getGroupId(), pg.getId().getValue()); + assertEquals(apiPresentationGroup.getLastModified(), pg.lastModifyTime); + assertMatch(apiPresentationGroup.getElements(), pg.dataPresentations); + } + + @Test + void testDataPresentationMap() throws Exception + { + List dataPresentations = new ArrayList<>(); + DataPresentation dataPres = new DataPresentation(); + dataPres.setDataType(new DataType("New Data Type", "TST")); + dataPres.setId(DbKey.createDbKey(1234567L)); + dataPres.setMaxDecimals(2); + dataPres.setMaxValue(100.0); + dataPres.setMinValue(0.0); + dataPres.setUnitsAbbr("TST"); + dataPresentations.add(dataPres); + + List elements = map(dataPresentations); + + assertNotNull(elements); + assertEquals(1, elements.size()); + ApiPresentationElement element = elements.get(0); + assertEquals(dataPres.getDataType().getCode(), element.getDataTypeCode()); + assertEquals(dataPres.getUnitsAbbr(), element.getUnits()); + assertEquals(dataPres.getMaxDecimals(), element.getFractionalDigits()); + assertEquals(dataPres.getMaxValue(), element.getMax()); + assertEquals(dataPres.getMinValue(), element.getMin()); + } + + private void assertMatch(List elements, Vector dataPresentations) + { + assertEquals(elements.size(), dataPresentations.size()); + int index = 0; + for (DataPresentation presentation : dataPresentations) + { + ApiPresentationElement element = elements.get(index); + assertEquals(element.getDataTypeCode(), presentation.getDataType().getCode()); + assertEquals(element.getUnits(), presentation.getUnitsAbbr()); + assertEquals(element.getFractionalDigits(), presentation.getMaxDecimals()); + assertEquals(element.getMax(), presentation.getMaxValue()); + assertEquals(element.getMin(), presentation.getMinValue()); + index++; + } + } + + +} \ No newline at end of file diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/PresentationResourcesIT.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/PresentationResourcesIT.java new file mode 100644 index 000000000..7831ed9b1 --- /dev/null +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/PresentationResourcesIT.java @@ -0,0 +1,326 @@ +package org.opendcs.odcsapi.res.it; + +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.filter.log.LogDetail; +import io.restassured.filter.session.SessionFilter; +import io.restassured.path.json.JsonPath; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; +import org.opendcs.odcsapi.beans.ApiPresentationGroup; +import org.opendcs.odcsapi.fixtures.DatabaseContextProvider; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +@Tag("integration-opentsdb-only") +@ExtendWith(DatabaseContextProvider.class) +final class PresentationResourcesIT extends BaseIT +{ + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static SessionFilter sessionFilter; + private Long parentPresentationId; + private Long presentationId; + + @BeforeEach + void setUp() throws Exception + { + setUpCreds(); + sessionFilter = new SessionFilter(); + authenticate(sessionFilter); + + String presentationJson = getJsonFromResource("presentation_insert_parent_data.json"); + + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .body(presentationJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)) + .extract() + ; + + parentPresentationId = response.jsonPath().getLong("groupId"); + + ApiPresentationGroup presentationGroup = getDtoFromResource("presentation_insert_data.json", + ApiPresentationGroup.class); + presentationGroup.setInheritsFromId(parentPresentationId); + presentationJson = MAPPER.writeValueAsString(presentationGroup); + + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .body(presentationJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)) + .extract() + ; + + presentationId = response.jsonPath().getLong("groupId"); + } + + @AfterEach + void tearDown() + { + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .queryParam("groupid", presentationId) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)) + ; + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .queryParam("groupid", parentPresentationId) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)) + ; + + logout(sessionFilter); + } + + @TestTemplate + void testGetPresentationRefs() + { + JsonPath expected = getJsonPathFromResource("presentation_get_refs_expected.json"); + + Map expectedItem1 = (Map) expected.getList("").get(0); + Map expectedItem2 = (Map) expected.getList("").get(1); + + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("presentationrefs") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract() + ; + + JsonPath actual = response.jsonPath(); + List> actualList = actual.getList(""); + for (Map actualItem : actualList) + { + if (actualItem.get("name").equals(expectedItem1.get("name"))) + { + assertEquals(expectedItem1.get("name"), actualItem.get("name")); + assertEquals(expectedItem1.get("inheritsFrom"), actualItem.get("inheritsFrom")); + assertEquals(expectedItem1.get("production"), actualItem.get("production")); + assertEquals(expectedItem1.get("lastModified"), actualItem.get("lastModified")); + assertEquals(-1, actualItem.get("inheritsFromId")); + } + else if (actualItem.get("name").equals(expectedItem2.get("name"))) + { + assertEquals(expectedItem2.get("name"), actualItem.get("name")); + assertEquals(expectedItem2.get("inheritsFrom"), actualItem.get("inheritsFrom")); + assertEquals(expectedItem2.get("production"), actualItem.get("production")); + assertEquals(expectedItem2.get("lastModified"), actualItem.get("lastModified")); + assertNotEquals(-1, actualItem.get("inheritsFromId")); + assertEquals((int) actualItem.get("inheritsFromId"), parentPresentationId.intValue()); + } + } + } + + @TestTemplate + void testGetPresentation() throws Exception + { + ApiPresentationGroup presentationGroup = getDtoFromResource("presentation_insert_data.json", + ApiPresentationGroup.class); + presentationGroup.setInheritsFromId(parentPresentationId); + + String presentationJson = MAPPER.writeValueAsString(presentationGroup); + JsonPath expected = new JsonPath(presentationJson); + + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .queryParam("groupid", presentationId) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract() + ; + + JsonPath actual = response.jsonPath(); + + assertMatch(expected, actual); + } + + @TestTemplate + void testPostAndDeletePresentation() throws Exception + { + String presentationJson = getJsonFromResource("presentation_post_delete_insert_data.json"); + + // Store the new presentation group + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .body(presentationJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)) + .extract() + ; + + Long newPresentationId = response.jsonPath().getLong("groupId"); + + // Retrieve the new presentation group and assert the data matches expected values + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .queryParam("groupid", newPresentationId) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract() + ; + + JsonPath actual = response.jsonPath(); + + JsonPath expected = new JsonPath(presentationJson); + + assertMatch(expected, actual); + + // Delete the new presentation group + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .queryParam("groupid", newPresentationId) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)) + ; + + // Retrieve the presentation group and assert it was deleted + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .queryParam("groupid", newPresentationId) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("presentation") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)) + ; + } + + private void assertMatch(JsonPath expected, JsonPath actual) + { + Map actualItem = actual.getMap(""); + Map expectedMap = expected.getMap(""); + assertEquals(expectedMap.get("name"), actualItem.get("name")); + assertEquals(expectedMap.get("inheritsFrom"), actualItem.get("inheritsFrom")); + assertEquals(expectedMap.get("production"), actualItem.get("production")); + List> elements = (List>) expectedMap.get("elements"); + List> actualElements = (List>) actualItem.get("elements"); + for(Map stringObjectMap : elements) + { + for(Map objectMap : actualElements) + { + if(stringObjectMap.get("dataTypeCode").equals(objectMap.get("dataTypeCode"))) + { + assertEquals(stringObjectMap.get("dataTypeCode"), objectMap.get("dataTypeCode")); + assertEquals(stringObjectMap.get("max"), objectMap.get("max")); + assertEquals(stringObjectMap.get("min"), objectMap.get("min")); + assertEquals(stringObjectMap.get("units"), objectMap.get("units")); + assertEquals(stringObjectMap.get("dataTypeStd"), objectMap.get("dataTypeStd")); + assertEquals(stringObjectMap.get("fractionalDigits"), objectMap.get("fractionalDigits")); + } + } + } + } +} diff --git a/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_get_refs_expected.json b/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_get_refs_expected.json new file mode 100644 index 000000000..9b3c0b4f5 --- /dev/null +++ b/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_get_refs_expected.json @@ -0,0 +1,18 @@ +[ + { + "groupId": 5, + "name": "Parent Presentation Group", + "inheritsFrom": null, + "inheritsFromId": -1, + "lastModified": "2025-01-10T17:38:35.000Z[UTC]", + "production": true + }, + { + "groupId": 6, + "name": "Test Presentation Group", + "inheritsFrom": "Parent Presentation Group", + "inheritsFromId": 5, + "lastModified": "2025-01-10T17:38:35.000Z[UTC]", + "production": true + } +] \ No newline at end of file diff --git a/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_insert_data.json b/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_insert_data.json new file mode 100644 index 000000000..449ecb3ab --- /dev/null +++ b/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_insert_data.json @@ -0,0 +1,26 @@ +{ + "groupId" : null, + "name" : "Test Presentation Group", + "inheritsFrom" : "Parent Presentation Group", + "inheritsFromId" : null, + "lastModified" : 1736530715000, + "elements" : [ + { + "dataTypeStd" : "CWMS", + "dataTypeCode" : "Speed", + "units" : "kph", + "fractionalDigits" : 2, + "min" : 0.0, + "max" : 1000.0 + }, + { + "dataTypeStd" : "NRCS", + "dataTypeCode" : "PRES", + "units" : "pa", + "fractionalDigits" : 2, + "min" : 0.0, + "max" : 15000.0 + } + ], + "production" : true +} \ No newline at end of file diff --git a/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_insert_parent_data.json b/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_insert_parent_data.json new file mode 100644 index 000000000..c9392852d --- /dev/null +++ b/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_insert_parent_data.json @@ -0,0 +1,18 @@ +{ + "groupId" : null, + "name" : "Parent Presentation Group", + "inheritsFrom" : null, + "inheritsFromId" : null, + "lastModified" : 1736530715000, + "elements" : [ + { + "dataTypeStd" : "CWMS", + "dataTypeCode" : "Volume", + "units" : "km3", + "fractionalDigits" : 2, + "min" : 0.0, + "max" : 1000.0 + } + ], + "production" : true +} \ No newline at end of file diff --git a/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_post_delete_insert_data.json b/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_post_delete_insert_data.json new file mode 100644 index 000000000..c0e5823eb --- /dev/null +++ b/opendcs-rest-api/src/test/resources/org/opendcs/odcsapi/res/it/OPEN_TSDB/presentation_post_delete_insert_data.json @@ -0,0 +1,26 @@ +{ + "groupId" : null, + "name" : "TS Presentation", + "inheritsFrom" : null, + "inheritsFromId" : null, + "lastModified" : 1736530715000, + "elements" : [ + { + "dataTypeStd" : "CWMS", + "dataTypeCode" : "Speed", + "units" : "mph", + "fractionalDigits" : 2, + "min" : 0.0, + "max" : 1500.0 + }, + { + "dataTypeStd" : "NRCS", + "dataTypeCode" : "PRES", + "units" : "kPa", + "fractionalDigits" : 2, + "min" : -10.0, + "max" : 150.0 + } + ], + "production" : true +} \ No newline at end of file