From 39fdb1e691464b1fd08642effb6a050a50838b6c Mon Sep 17 00:00:00 2001 From: zack-rma Date: Mon, 7 Oct 2024 16:30:52 -0700 Subject: [PATCH 1/2] CTO-118 Updated Gate Change Controller and DAO to support operation changes store/delete functionality --- .../kind/GateChangeCreateController.java | 39 ++-- .../api/location/kind/OutletController.java | 182 +++++++++--------- .../cda/data/dao/location/kind/OutletDao.java | 24 ++- .../data/dto/location/kind/GateChange.java | 26 ++- .../data/dto/location/kind/GateSetting.java | 16 ++ .../cda/data/dto/location/kind/Setting.java | 3 +- .../kind/GateChangeControllerTestIT.java | 47 +++++ .../location/kind/OutletControllerTestIT.java | 4 +- .../kind/VirtualOutletControllerTestIT.java | 2 + .../dto/location/kind/GateChangeTest.java | 1 + .../resources/cwms/cda/api/gate_change.json | 39 ++++ .../data/dto/location/kind/gate-change.json | 1 + .../data/dto/location/kind/gate-changes.json | 2 + 13 files changed, 271 insertions(+), 115 deletions(-) create mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/gate_change.json diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeCreateController.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeCreateController.java index 0fb91c8d6..c9c92b09f 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeCreateController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeCreateController.java @@ -20,6 +20,10 @@ package cwms.cda.api.location.kind; +import static cwms.cda.api.Controllers.CREATE; +import static cwms.cda.api.Controllers.FAIL_IF_EXISTS; +import static cwms.cda.api.Controllers.STATUS_201; + import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import cwms.cda.api.BaseHandler; @@ -40,7 +44,7 @@ import javax.servlet.http.HttpServletResponse; import org.jetbrains.annotations.NotNull; import org.jooq.DSLContext; -import static cwms.cda.api.Controllers.*; + public class GateChangeCreateController extends BaseHandler { @@ -49,22 +53,22 @@ public GateChangeCreateController(MetricRegistry metrics) { } @OpenApi( - requestBody = @OpenApiRequestBody( - content = { - @OpenApiContent(from = GateChange.class, isArray = true, type = Formats.JSONV1), - @OpenApiContent(from = GateChange.class, isArray = true, type = Formats.JSON) - }, - required = true), - queryParams = { - @OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class, - description = "Create will fail if provided Gate Changes already exist. Default: true") + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(from = GateChange.class, isArray = true, type = Formats.JSONV1), + @OpenApiContent(from = GateChange.class, isArray = true, type = Formats.JSON) }, - description = "Create CWMS Gate Changes", - method = HttpMethod.POST, - tags = {OutletController.TAG}, - responses = { - @OpenApiResponse(status = STATUS_201, description = "Gate Changes successfully stored to CWMS.") - } + required = true), + queryParams = { + @OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class, + description = "Create will fail if provided Gate Changes already exist. Default: true") + }, + description = "Create CWMS Gate Changes", + method = HttpMethod.POST, + tags = {OutletController.TAG}, + responses = { + @OpenApiResponse(status = STATUS_201, description = "Gate Changes successfully stored to CWMS.") + } ) @Override public void handle(@NotNull Context context) throws Exception { @@ -73,6 +77,9 @@ public void handle(@NotNull Context context) throws Exception { ContentType contentType = Formats.parseHeader(formatHeader, GateChange.class); List changes = Formats.parseContentList(contentType, context.body(), GateChange.class); + // Sort changes by date to avoid DB errors + changes.sort(GateChange::compareTo); + try (Timer.Context ignored = markAndTime(CREATE)) { DSLContext dsl = JooqDao.getDslContext(context); OutletDao dao = new OutletDao(dsl); diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/OutletController.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/OutletController.java index 8fcabea7e..640eaf652 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/OutletController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/OutletController.java @@ -20,17 +20,30 @@ package cwms.cda.api.location.kind; -import com.codahale.metrics.Histogram; +import static cwms.cda.api.Controllers.CREATE; +import static cwms.cda.api.Controllers.DELETE; +import static cwms.cda.api.Controllers.FAIL_IF_EXISTS; +import static cwms.cda.api.Controllers.GET_ALL; +import static cwms.cda.api.Controllers.GET_ONE; +import static cwms.cda.api.Controllers.METHOD; +import static cwms.cda.api.Controllers.NAME; +import static cwms.cda.api.Controllers.OFFICE; +import static cwms.cda.api.Controllers.PROJECT_ID; +import static cwms.cda.api.Controllers.STATUS_200; +import static cwms.cda.api.Controllers.STATUS_204; +import static cwms.cda.api.Controllers.STATUS_404; +import static cwms.cda.api.Controllers.queryParamAsClass; +import static cwms.cda.api.Controllers.requiredParam; +import static cwms.cda.data.dao.JooqDao.getDslContext; + import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import cwms.cda.api.BaseCrudHandler; -import cwms.cda.api.Controllers; import cwms.cda.data.dao.JooqDao; import cwms.cda.data.dao.location.kind.OutletDao; import cwms.cda.data.dto.location.kind.Outlet; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; -import io.javalin.apibuilder.CrudHandler; import io.javalin.core.util.Header; import io.javalin.http.Context; import io.javalin.plugin.openapi.annotations.HttpMethod; @@ -43,9 +56,7 @@ import javax.servlet.http.HttpServletResponse; import org.jetbrains.annotations.NotNull; import org.jooq.DSLContext; -import static com.codahale.metrics.MetricRegistry.name; -import static cwms.cda.api.Controllers.*; -import static cwms.cda.data.dao.JooqDao.getDslContext; + public class OutletController extends BaseCrudHandler { static final String TAG = "Outlets"; @@ -55,22 +66,22 @@ public OutletController(MetricRegistry metrics) { } @OpenApi( - requestBody = @OpenApiRequestBody( - content = { - @OpenApiContent(from = Outlet.class, type = Formats.JSONV1), - @OpenApiContent(from = Outlet.class, type = Formats.JSON) - }, - required = true), - queryParams = { - @OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class, - description = "Create will fail if provided ID already exists. Default: true") + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(from = Outlet.class, type = Formats.JSONV1), + @OpenApiContent(from = Outlet.class, type = Formats.JSON) }, - description = "Create CWMS Outlet", - method = HttpMethod.POST, - tags = {TAG}, - responses = { - @OpenApiResponse(status = STATUS_204, description = "Outlet successfully stored to CWMS.") - } + required = true), + queryParams = { + @OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class, + description = "Create will fail if provided ID already exists. Default: true") + }, + description = "Create CWMS Outlet", + method = HttpMethod.POST, + tags = {TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "Outlet successfully stored to CWMS.") + } ) @Override public void create(@NotNull Context ctx) { @@ -88,20 +99,20 @@ public void create(@NotNull Context ctx) { } @OpenApi( - queryParams = { - @OpenApiParam(name = OFFICE, required = true, description = "Office id for the reservoir project location " + - "associated with the outlets."), - @OpenApiParam(name = PROJECT_ID, required = true, description = "Specifies the project-id of the " + - "Outlets whose data is to be included in the response."), - }, - responses = { - @OpenApiResponse(status = STATUS_200, content = { - @OpenApiContent(from = Outlet.class, isArray = true, type = Formats.JSONV1), - @OpenApiContent(from = Outlet.class, isArray = true, type = Formats.JSON) - }) - }, - description = "Returns matching CWMS Outlet Data for a Reservoir Project.", - tags = {TAG} + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Office id for the reservoir project location " + + "associated with the outlets."), + @OpenApiParam(name = PROJECT_ID, required = true, description = "Specifies the project-id of the " + + "Outlets whose data is to be included in the response."), + }, + responses = { + @OpenApiResponse(status = STATUS_200, content = { + @OpenApiContent(from = Outlet.class, isArray = true, type = Formats.JSONV1), + @OpenApiContent(from = Outlet.class, isArray = true, type = Formats.JSON) + }) + }, + description = "Returns matching CWMS Outlet Data for a Reservoir Project.", + tags = {TAG} ) @Override public void getAll(@NotNull Context ctx) { @@ -122,23 +133,23 @@ public void getAll(@NotNull Context ctx) { } @OpenApi( - pathParams = { - @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of the " + - "Outlet to be created."), - }, - queryParams = { - @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " - + "the outlet to be retrieved."), - }, - responses = { - @OpenApiResponse(status = STATUS_200, - content = { - @OpenApiContent(from = Outlet.class, type = Formats.JSONV1), - @OpenApiContent(from = Outlet.class, type = Formats.JSON) - }) - }, - description = "Returns CWMS Outlet Data", - tags = {TAG} + pathParams = { + @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of the " + + "Outlet to be created."), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " + + "the outlet to be retrieved."), + }, + responses = { + @OpenApiResponse(status = STATUS_200, + content = { + @OpenApiContent(from = Outlet.class, type = Formats.JSONV1), + @OpenApiContent(from = Outlet.class, type = Formats.JSON) + }) + }, + description = "Returns CWMS Outlet Data", + tags = {TAG} ) @Override public void getOne(@NotNull Context ctx, @NotNull String name) { @@ -158,21 +169,21 @@ public void getOne(@NotNull Context ctx, @NotNull String name) { } @OpenApi( - pathParams = { - @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of " - + "the outlet to be renamed."), - }, - queryParams = { - @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " - + "the outlet to be renamed."), - @OpenApiParam(name = NAME, required = true, description = "Specifies the new outlet location-id."), - }, - description = "Rename CWMS Outlet", - method = HttpMethod.PATCH, - tags = {TAG}, - responses = { - @OpenApiResponse(status = STATUS_204, description = "CWMS Outlet successfully renamed.") - } + pathParams = { + @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of " + + "the outlet to be renamed."), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " + + "the outlet to be renamed."), + @OpenApiParam(name = NAME, required = true, description = "Specifies the new outlet location-id."), + }, + description = "Rename CWMS Outlet", + method = HttpMethod.PATCH, + tags = {TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "CWMS Outlet successfully renamed.") + } ) @Override public void update(@NotNull Context ctx, @NotNull String name) { @@ -187,25 +198,24 @@ public void update(@NotNull Context ctx, @NotNull String name) { } @OpenApi( - pathParams = { - @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of the outlet to be" + - " deleted."), - }, - queryParams = { - @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " - + "the outlet to be deleted."), - @OpenApiParam(name = METHOD, description = "Specifies the delete method used. " + - "Defaults to \"DELETE_KEY\"", - type = JooqDao.DeleteMethod.class) - }, - description = "Delete CWMS Outlet", - method = HttpMethod.DELETE, - tags = {TAG}, - responses = { - @OpenApiResponse(status = STATUS_204, description = "Outlet successfully deleted from CWMS."), - @OpenApiResponse(status = STATUS_404, description = "Based on the combination of " - + "inputs provided the outlet was not found.") - } + pathParams = { + @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of the outlet to be" + + " deleted."), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " + + "the outlet to be deleted."), + @OpenApiParam(name = METHOD, description = "Specifies the delete method used. " + + "Defaults to \"DELETE_KEY\"", type = JooqDao.DeleteMethod.class) + }, + description = "Delete CWMS Outlet", + method = HttpMethod.DELETE, + tags = {TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "Outlet successfully deleted from CWMS."), + @OpenApiResponse(status = STATUS_404, description = "Based on the combination of " + + "inputs provided the outlet was not found.") + } ) @Override public void delete(@NotNull Context ctx, @NotNull String name) { diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java index a3f418723..50c86bf31 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java @@ -20,6 +20,8 @@ package cwms.cda.data.dao.location.kind; +import static cwms.cda.data.dao.location.kind.LocationUtil.getLocationRef; + import cwms.cda.api.enums.UnitSystem; import cwms.cda.api.errors.NotFoundException; import cwms.cda.data.dao.DeleteRule; @@ -45,6 +47,7 @@ import java.util.stream.Collectors; import org.jooq.Configuration; import org.jooq.DSLContext; +import org.jooq.exception.IntegrityConstraintViolationException; import org.jooq.impl.DSL; import usace.cwms.db.jooq.codegen.packages.CWMS_OUTLET_PACKAGE; import usace.cwms.db.jooq.codegen.udt.records.GATE_CHANGE_OBJ_T; @@ -55,7 +58,7 @@ import usace.cwms.db.jooq.codegen.udt.records.PROJECT_STRUCTURE_OBJ_T; import usace.cwms.db.jooq.codegen.udt.records.STR_TAB_T; import usace.cwms.db.jooq.codegen.udt.records.STR_TAB_TAB_T; -import static cwms.cda.data.dao.location.kind.LocationUtil.getLocationRef; + public class OutletDao extends JooqDao { @@ -114,8 +117,12 @@ public void storeOutlet(Outlet outlet, boolean failIfExists) { public void deleteOutlet(String officeId, String locationId, DeleteRule deleteRule) { connection(dsl, conn -> { setOffice(conn, officeId); - CWMS_OUTLET_PACKAGE.call_DELETE_OUTLET(DSL.using(conn).configuration(), locationId, deleteRule.getRule(), - officeId); + try { + CWMS_OUTLET_PACKAGE.call_DELETE_OUTLET(DSL.using(conn).configuration(), locationId, + deleteRule.getRule(), officeId); + } catch (IntegrityConstraintViolationException e) { + throw new NotFoundException(e); + } }); } @@ -257,8 +264,8 @@ public void renameOutlet(String officeId, String oldOutletId, String newOutletId } public List retrieveOperationalChanges(CwmsId projectId, Instant startTime, Instant endTime, - boolean startInclusive, boolean endInclusive, UnitSystem unitSystem, - long rowLimit) { + boolean startInclusive, boolean endInclusive, + UnitSystem unitSystem, long rowLimit) { return connectionResult(dsl, conn -> { setOffice(conn, projectId.getOfficeId()); @@ -267,11 +274,12 @@ public List retrieveOperationalChanges(CwmsId projectId, Instant sta Timestamp endTimestamp = Timestamp.from(endTime); BigInteger rowLimitBig = BigInteger.valueOf(rowLimit); GATE_CHANGE_TAB_T changeTab = CWMS_OUTLET_PACKAGE.call_RETRIEVE_GATE_CHANGES( - DSL.using(conn).configuration(), locationRef, startTimestamp, endTimestamp, "UTC", unitSystem.getValue(), - formatBool(startInclusive), formatBool(endInclusive), rowLimitBig); + DSL.using(conn).configuration(), locationRef, startTimestamp, endTimestamp, "UTC", + unitSystem.getValue(), formatBool(startInclusive), formatBool(endInclusive), rowLimitBig); if (changeTab == null) { - throw new NotFoundException("No changes found for " + projectId.getOfficeId() + "." + projectId.getName() + throw new NotFoundException("No changes found for " + projectId.getOfficeId() + "." + + projectId.getName() + "\nStart time: " + startTime + "\nEnd time: " + endTime + "\nStart inclusive: " + startInclusive diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/GateChange.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/GateChange.java index f10b30c86..b333cff3d 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/GateChange.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/GateChange.java @@ -20,9 +20,12 @@ package cwms.cda.data.dto.location.kind; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonNaming; @@ -30,15 +33,21 @@ import cwms.cda.formatters.Formats; import cwms.cda.formatters.annotations.FormattableWith; import cwms.cda.formatters.json.JsonV1; -import java.util.Objects; + @FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSON}) @JsonDeserialize(builder = GateChange.Builder.class) @JsonInclude(JsonInclude.Include.NON_NULL) +@JsonTypeName("gate-change") +@JsonIgnoreProperties(ignoreUnknown = true) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -@JsonPropertyOrder({"project-id", "change-date", "reference-elevation", "pool-elevation", "protected", "discharge-computation-type", "reason-type", "notes"}) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true) +@JsonPropertyOrder({"project-id", "change-date", "reference-elevation", "pool-elevation", "protected", + "discharge-computation-type", "reason-type", "notes", "type"}) public class GateChange extends PhysicalStructureChange { + @JsonProperty("reference-elevation") private final Double referenceElevation; + private static final String type = "gate-change"; GateChange(Builder builder) { super(builder); @@ -49,13 +58,22 @@ public Double getReferenceElevation() { return referenceElevation; } + public String getType() { + return type; + } + @Override protected void validateInternal(CwmsDTOValidator validator) { validator.required(getPoolElevation(), "pool-elevation"); super.validateInternal(validator); } + public int compareTo(GateChange other) { + return this.getChangeDate().compareTo(other.getChangeDate()); + } + public static final class Builder extends PhysicalStructureChange.Builder { + @JsonProperty("reference-elevation") private Double referenceElevation; public Builder() { @@ -72,6 +90,10 @@ public Builder referenceElevation(Double referenceElevation) { return this; } + public Builder withType(String type) { + return this; + } + @Override GateChange.Builder self() { return this; diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/GateSetting.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/GateSetting.java index 49e0f3ade..17cf084fe 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/GateSetting.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/GateSetting.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -33,7 +35,9 @@ @FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSON}) @JsonDeserialize(builder = GateSetting.Builder.class) @JsonInclude(JsonInclude.Include.NON_NULL) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@JsonSubTypes({@JsonSubTypes.Type(value = GateChange.class, name = "gate-change")}) @JsonTypeName("gate-setting") public final class GateSetting extends Setting { @JsonProperty(required = true) @@ -44,6 +48,7 @@ public final class GateSetting extends Setting { private final String openingUnits; @JsonProperty(required = true) private final Double invertElevation; + private final String type; private GateSetting(Builder builder) { super(builder); @@ -51,6 +56,7 @@ private GateSetting(Builder builder) { openingParameter = builder.openingParameter; openingUnits = builder.openingUnits; invertElevation = builder.invertElevation; + type = builder.type; } public Double getInvertElevation() { @@ -69,11 +75,16 @@ public String getOpeningUnits() { return openingUnits; } + public String getType() { + return type; + } + public static final class Builder extends Setting.Builder { private Double opening; private String openingParameter; private String openingUnits; private Double invertElevation; + private String type; @Override protected Builder self() { @@ -100,6 +111,11 @@ public Builder withInvertElevation(Double invertElevation) { return self(); } + public Builder withType(String type) { + this.type = type; + return self(); + } + @Override public GateSetting build() { return new GateSetting(this); diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/Setting.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/Setting.java index a94cb75ee..42fde8d6a 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/Setting.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/Setting.java @@ -31,7 +31,8 @@ import cwms.cda.data.dto.CwmsId; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({@JsonSubTypes.Type(value = TurbineSetting.class, name = "turbine-setting")}) +@JsonSubTypes({@JsonSubTypes.Type(value = TurbineSetting.class, name = "turbine-setting"), + @JsonSubTypes.Type(value = GateSetting.class, name = "gate-setting")}) public abstract class Setting extends CwmsDTOBase { @JsonProperty(required = true) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/location/kind/GateChangeControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/location/kind/GateChangeControllerTestIT.java index d091ff86c..75f27b356 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/location/kind/GateChangeControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/location/kind/GateChangeControllerTestIT.java @@ -35,6 +35,8 @@ import fixtures.CwmsDataApiSetupCallback; import io.restassured.filter.log.LogDetail; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZonedDateTime; import java.util.Arrays; @@ -42,16 +44,20 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; import mil.army.usace.hec.test.database.CwmsDatabaseContainer; +import org.apache.commons.io.IOUtils; import org.jooq.DSLContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static cwms.cda.api.Controllers.*; import static cwms.cda.data.dao.DaoTest.getDslContext; import static cwms.cda.security.KeyAccessManager.AUTH_HEADER; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; +@Tag("integration") class GateChangeControllerTestIT extends BaseOutletDaoIT { private static final FluentLogger LOGGER = FluentLogger.forEnclosingClass(); @@ -269,6 +275,47 @@ void test_changes_crud() { .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); } + @Test + void test_changes_create_from_file() throws Exception { + InputStream is = this.getClass().getResourceAsStream("/cwms/cda/api/gate_change.json"); + assertNotNull(is); + String json = IOUtils.toString(is, StandardCharsets.UTF_8); + List change = Formats.parseContentList(Formats.parseHeader(Formats.JSONV1, GateChange.class), json, GateChange.class); + + //Create the gate changes + given() + .log().ifValidationFails(LogDetail.ALL, true) + .contentType(Formats.JSONV1) + .body(json) + .header(AUTH_HEADER, USER.toHeaderValue()) + .queryParam(FAIL_IF_EXISTS, false) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("projects/gate-changes") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .header(AUTH_HEADER, USER.toHeaderValue()) + .queryParam(BEGIN, JAN_FIRST.toString()) + .queryParam(END, JAN_SECOND.toString()) + .queryParam(OVERRIDE_PROTECTION, "true") + .queryParam(FAIL_IF_EXISTS, "false") + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("projects/" + change.get(0).getProjectId().getOfficeId() + + "/" + change.get(0).getProjectId().getName() + "/gate-changes") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)); + } + private boolean isSimilar(GateChange left, GateChange right) { boolean output = false; diff --git a/cwms-data-api/src/test/java/cwms/cda/api/location/kind/OutletControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/location/kind/OutletControllerTestIT.java index b2980001e..d9930e357 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/location/kind/OutletControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/location/kind/OutletControllerTestIT.java @@ -20,7 +20,6 @@ package cwms.cda.api.location.kind; -import com.google.common.flogger.FluentLogger; import cwms.cda.api.Controllers; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.location.kind.BaseOutletDaoIT; @@ -39,6 +38,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static cwms.cda.api.Controllers.*; import static cwms.cda.data.dao.DaoTest.getDslContext; @@ -46,8 +46,8 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.*; +@Tag("integration") class OutletControllerTestIT extends BaseOutletDaoIT { - private static final FluentLogger LOGGER = FluentLogger.forEnclosingClass(); private static final CwmsId CONDUIT_GATE_RATING_GROUP = new CwmsId.Builder().withName( "Rating-" + PROJECT_1_ID.getName() + "-ConduitGate").withOfficeId(OFFICE_ID).build(); private static final CwmsId MODIFIED_CONDUIT_GATE_RATING_GROUP = new CwmsId.Builder().withName( diff --git a/cwms-data-api/src/test/java/cwms/cda/api/location/kind/VirtualOutletControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/location/kind/VirtualOutletControllerTestIT.java index 9373edfa2..2f6c6b2b1 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/location/kind/VirtualOutletControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/location/kind/VirtualOutletControllerTestIT.java @@ -40,6 +40,7 @@ import org.jooq.DSLContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static cwms.cda.api.Controllers.FAIL_IF_EXISTS; import static cwms.cda.api.Controllers.METHOD; @@ -48,6 +49,7 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.*; +@Tag("integration") class VirtualOutletControllerTestIT extends ProjectStructureIT { private static final String OUTLET_KIND = "OUTLET"; private static final CwmsId VIRTUAL_OUTLET_RATING_GROUP = new CwmsId.Builder().withName("Rating-" + PROJECT_LOC2.getName() + "-VirtualOutlet") diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/GateChangeTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/GateChangeTest.java index 5d90b4160..6236fcd66 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/GateChangeTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/GateChangeTest.java @@ -199,6 +199,7 @@ static GateChange buildTestGateChange(CwmsId projectId, Instant changeDate, Gate .withNotes(notes) .withChangeDate(changeDate) .withSettings(settings) +// .withType("gate-change") .build(); } } \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/gate_change.json b/cwms-data-api/src/test/resources/cwms/cda/api/gate_change.json new file mode 100644 index 000000000..bc9d51b0a --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/gate_change.json @@ -0,0 +1,39 @@ +[ { + "type" : "gate-change", + "project-id" : { + "office-id" : "SPK", + "name" : "PROJECT1" + }, + "change-date" : 1704096000000, + "pool-elevation" : 3.0, + "protected" : true, + "discharge-computation-type" : { + "office-id" : "SPK", + "display-value" : "A", + "tooltip" : "Adjusted by an automated method", + "active" : true + }, + "reason-type" : { + "office-id" : "SPK", + "display-value" : "O", + "tooltip" : "Other release", + "active" : true + }, + "notes" : "Test notes", + "new-total-discharge-override" : 1.0, + "old-total-discharge-override" : 2.0, + "discharge-units" : "cfs", + "tailwater-elevation" : 4.0, + "elevation-units" : "ft", + "settings" : [ { + "type" : "gate-setting", + "location-id" : { + "office-id" : "SPK", + "name" : "PROJECT1-CG100" + }, + "opening" : 10.0, + "opening-parameter" : "Opening", + "opening-units" : "ft", + "invert-elevation" : 20.0 + } ] +} ] \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/gate-change.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/gate-change.json index 9cc7ff0e8..95bd60de1 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/gate-change.json +++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/gate-change.json @@ -1,4 +1,5 @@ { + "type": "gate-change", "project-id": { "office-id": "SPK", "name": "BIGH" diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/gate-changes.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/gate-changes.json index 06cd71a15..b061dbdf9 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/gate-changes.json +++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/gate-changes.json @@ -1,5 +1,6 @@ [ { + "type": "gate-change", "project-id": { "office-id": "SPK", "name": "BIGH" @@ -51,6 +52,7 @@ ] }, { + "type": "gate-change", "project-id": { "office-id": "SPK", "name": "BIGH" From d4745f53778e6ce2a4f30d90c846754454c5f9e5 Mon Sep 17 00:00:00 2001 From: zack-rma Date: Tue, 8 Oct 2024 09:36:28 -0700 Subject: [PATCH 2/2] CTO-118 - Cleanup up project naming, error response per Mike's feedback --- .../cda/data/dao/location/kind/OutletDao.java | 6 +++++- .../kind/GateChangeControllerTestIT.java | 21 +++++++++++++++++++ .../dto/location/kind/GateChangeTest.java | 1 - .../resources/cwms/cda/api/gate_change.json | 4 ++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java index 50c86bf31..9c8d49e53 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java @@ -23,6 +23,7 @@ import static cwms.cda.data.dao.location.kind.LocationUtil.getLocationRef; import cwms.cda.api.enums.UnitSystem; +import cwms.cda.api.errors.DeleteConflictException; import cwms.cda.api.errors.NotFoundException; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.JooqDao; @@ -39,6 +40,7 @@ import cwms.cda.data.dto.location.kind.VirtualOutlet; import cwms.cda.data.dto.location.kind.VirtualOutletRecord; import java.math.BigInteger; +import java.sql.SQLException; import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; @@ -121,7 +123,9 @@ public void deleteOutlet(String officeId, String locationId, DeleteRule deleteRu CWMS_OUTLET_PACKAGE.call_DELETE_OUTLET(DSL.using(conn).configuration(), locationId, deleteRule.getRule(), officeId); } catch (IntegrityConstraintViolationException e) { - throw new NotFoundException(e); + SQLException cause = e.getCause(SQLException.class); + throw new DeleteConflictException("Cannot delete outlet " + locationId + " because of an integrity" + + " constraint violation.", cause); } }); } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/location/kind/GateChangeControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/location/kind/GateChangeControllerTestIT.java index 75f27b356..968f9320f 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/location/kind/GateChangeControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/location/kind/GateChangeControllerTestIT.java @@ -50,6 +50,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import usace.cwms.db.jooq.codegen.packages.CWMS_PROJECT_PACKAGE; + import static cwms.cda.api.Controllers.*; import static cwms.cda.data.dao.DaoTest.getDslContext; import static cwms.cda.security.KeyAccessManager.AUTH_HEADER; @@ -281,6 +283,25 @@ void test_changes_create_from_file() throws Exception { assertNotNull(is); String json = IOUtils.toString(is, StandardCharsets.UTF_8); List change = Formats.parseContentList(Formats.parseHeader(Formats.JSONV1, GateChange.class), json, GateChange.class); + String office = change.get(0).getProjectId().getOfficeId(); + String project = change.get(0).getProjectId().getName(); + String location = change.get(0).getSettings().stream().findFirst().get().getLocationId().getName(); + Location projectLocation = buildProjectLocation(project); + createLocation(project, true, office, "PROJECT"); + createLocation(project + "-" + location, false, office); + String outletRatingSpecId = project + ".Opening-ConduitGate,Elev;Flow-ConduitGate.Standard.Production"; + CwmsId outletRatingGroup = new CwmsId.Builder().withName( + "Rating-" + project + "-ConduitGate").withOfficeId(office).build(); + Outlet outlet = buildTestOutlet(buildProjectStructureLocation(location, OUTLET_KIND), projectLocation, + outletRatingGroup, outletRatingSpecId); + + CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); + databaseLink.connection(c -> { + DSLContext context = getDslContext(c, office); + OutletDao outletDao = new OutletDao(context); + CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(context.configuration(), buildProject(projectLocation), "F"); + outletDao.storeOutlet(outlet, false); + }, CwmsDataApiSetupCallback.getWebUser()); //Create the gate changes given() diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/GateChangeTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/GateChangeTest.java index 6236fcd66..5d90b4160 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/GateChangeTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/GateChangeTest.java @@ -199,7 +199,6 @@ static GateChange buildTestGateChange(CwmsId projectId, Instant changeDate, Gate .withNotes(notes) .withChangeDate(changeDate) .withSettings(settings) -// .withType("gate-change") .build(); } } \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/gate_change.json b/cwms-data-api/src/test/resources/cwms/cda/api/gate_change.json index bc9d51b0a..90f1cacf6 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/api/gate_change.json +++ b/cwms-data-api/src/test/resources/cwms/cda/api/gate_change.json @@ -2,7 +2,7 @@ "type" : "gate-change", "project-id" : { "office-id" : "SPK", - "name" : "PROJECT1" + "name" : "BIGH" }, "change-date" : 1704096000000, "pool-elevation" : 3.0, @@ -29,7 +29,7 @@ "type" : "gate-setting", "location-id" : { "office-id" : "SPK", - "name" : "PROJECT1-CG100" + "name" : "BIGH-CG100" }, "opening" : 10.0, "opening-parameter" : "Opening",