From c28647493afdb7e2428f0423e27c8ca04e022db1 Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Wed, 25 Sep 2024 17:17:47 -0700 Subject: [PATCH 01/10] CWDB-225 - Implemented Measurement Controller --- .../src/main/java/cwms/cda/ApiServlet.java | 7 + .../main/java/cwms/cda/api/Controllers.java | 5 + .../cwms/cda/api/MeasurementController.java | 247 +++++++ .../cda/api/MeasurementPatchController.java | 89 +++ .../cda/data/dto/measurement/Measurement.java | 2 + .../cda/api/MeasurementControllerTestIT.java | 630 ++++++++++++++++++ .../resources/cwms/cda/api/measurement.json | 49 ++ .../cwms/cda/api/measurement_updated.json | 49 ++ .../resources/cwms/cda/api/measurements.json | 100 +++ .../cwms/cda/api/measurements_updated.json | 100 +++ 10 files changed, 1278 insertions(+) create mode 100644 cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java create mode 100644 cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java create mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/measurement.json create mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/measurement_updated.json create mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/measurements.json create mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/measurements_updated.json diff --git a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java index b4f0a4ba2..e82b59349 100644 --- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java @@ -26,10 +26,12 @@ import static cwms.cda.api.Controllers.CONTRACT_NAME; +import static cwms.cda.api.Controllers.LOCATION_ID; 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.WATER_USER; +import cwms.cda.api.MeasurementPatchController; import static io.javalin.apibuilder.ApiBuilder.crud; import static io.javalin.apibuilder.ApiBuilder.delete; import static io.javalin.apibuilder.ApiBuilder.get; @@ -214,6 +216,7 @@ "/streams/*", "/stream-locations/*", "/stream-reaches/*", + "/measurements/*", "/blobs/*", "/clobs/*", "/pools/*", @@ -538,6 +541,10 @@ protected void configureRoutes() { new StreamLocationController(metrics), requiredRoles,5, TimeUnit.MINUTES); cdaCrudCache(format("/stream-reaches/{%s}", NAME), new StreamReachController(metrics), requiredRoles,1, TimeUnit.DAYS); + String measurements = "/measurements/"; + patch(measurements ,new MeasurementPatchController(metrics)); + cdaCrudCache(format(measurements + "{%s}", LOCATION_ID), + new cwms.cda.api.MeasurementController(metrics), requiredRoles,5, TimeUnit.MINUTES); cdaCrudCache("/blobs/{blob-id}", new BlobController(metrics), requiredRoles,5, TimeUnit.MINUTES); cdaCrudCache("/clobs/{clob-id}", diff --git a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java index fb36e3dd4..f3a689ca2 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java @@ -135,6 +135,11 @@ public final class Controllers { public static final String LOCATION_KIND_LIKE = "location-kind-like"; public static final String LOCATION_TYPE_LIKE = "location-type-like"; + public static final String MIN_DATE = "min-date"; + public static final String MAX_DATE = "max-date"; + public static final String MIN_NUMBER = "min-number"; + public static final String MAX_NUMBER = "max-number"; + public static final String GROUP_ID = "group-id"; public static final String REPLACE_ASSIGNED_LOCS = "replace-assigned-locs"; public static final String REPLACE_ASSIGNED_TS = "replace-assigned-ts"; diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java new file mode 100644 index 000000000..377daa3b3 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java @@ -0,0 +1,247 @@ +/* + * MIT License + * + * Copyright (c) 2024 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package cwms.cda.api; + +import com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import static com.codahale.metrics.MetricRegistry.name; +import com.codahale.metrics.Timer; +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.ID_MASK; +import static cwms.cda.api.Controllers.MAX_DATE; +import static cwms.cda.api.Controllers.MIN_DATE; +import static cwms.cda.api.Controllers.NOT_SUPPORTED_YET; +import static cwms.cda.api.Controllers.MIN_NUMBER; +import static cwms.cda.api.Controllers.MAX_NUMBER; +import static cwms.cda.api.Controllers.OFFICE; +import static cwms.cda.api.Controllers.OFFICE_MASK; +import static cwms.cda.api.Controllers.UNIT_SYSTEM; +import static cwms.cda.api.Controllers.requiredParam; +import cwms.cda.api.enums.UnitSystem; +import cwms.cda.data.dao.MeasurementDao; +import cwms.cda.data.dto.measurement.Measurement; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.FormattingException; +import io.javalin.apibuilder.CrudHandler; +import io.javalin.core.util.Header; +import io.javalin.http.Context; +import io.javalin.plugin.openapi.annotations.HttpMethod; +import io.javalin.plugin.openapi.annotations.OpenApi; +import io.javalin.plugin.openapi.annotations.OpenApiContent; +import io.javalin.plugin.openapi.annotations.OpenApiParam; +import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; +import io.javalin.plugin.openapi.annotations.OpenApiResponse; +import java.time.Instant; +import java.util.ArrayList; +import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +import static cwms.cda.data.dao.JooqDao.getDslContext; + +public final class MeasurementController implements CrudHandler { + + static final String TAG = "Measurements"; + + private final MetricRegistry metrics; + private final Histogram requestResultSize; + + public MeasurementController(MetricRegistry metrics) { + this.metrics = metrics; + String className = this.getClass().getName(); + requestResultSize = this.metrics.histogram(name(className, "results", "size")); + } + + private Timer.Context markAndTime(String subject) { + return Controllers.markAndTime(metrics, getClass().getName(), subject); + } + + @OpenApi( + queryParams = { + @OpenApiParam(name = OFFICE_MASK, description = "Office id for filtering measurements."), + @OpenApiParam(name = ID_MASK, description = "Location id for filtering measurements."), + @OpenApiParam(name = MIN_NUMBER, description = "Minimum number for filtering measurements."), + @OpenApiParam(name = MAX_NUMBER, description = "Maximum number for filtering measurements."), + @OpenApiParam(name = MIN_DATE, description = "Minimum date for filtering measurements."), + @OpenApiParam(name = MAX_DATE, description = "Maximum date for filtering measurements."), + @OpenApiParam(name = UNIT_SYSTEM, description = "Specifies the unit system" + + " of the response. Valid values for the unit field are: " + + "\n* `EN` Specifies English unit system. Location values will be in the " + + "default English units for their parameters." + + "\n* `SI` Specifies the SI unit system. Location values will be in the " + + "default SI units for their parameters. If not specified, EN is used.") + }, + responses = { + @OpenApiResponse(status = "200", content = { + @OpenApiContent(isArray = true, type = Formats.JSONV1, from = Measurement.class), + @OpenApiContent(isArray = true, type = Formats.JSON, from = Measurement.class) + }) + }, + description = "Returns matching measurement data.", + tags = {TAG} + ) + @Override + public void getAll(@NotNull Context ctx) { + String officeId = ctx.queryParam(OFFICE_MASK); + String locationId = ctx.queryParam(ID_MASK); + String unitSystem = ctx.queryParamAsClass(UNIT_SYSTEM, String.class).getOrDefault(UnitSystem.EN.value()); + Instant minDate = parseInstant(ctx.queryParam(MIN_DATE)); + Instant maxDate = parseInstant(ctx.queryParam(MAX_DATE)); + String minNum = ctx.queryParam(MIN_NUMBER); + String maxNum = ctx.queryParam(MAX_NUMBER); + Number minHeight = null; + Number maxHeight = null; + Number minFlow = null; + Number maxFlow = null; + String agencies = null; + String qualities = null; + try (Timer.Context ignored = markAndTime(GET_ALL)) { + DSLContext dsl = getDslContext(ctx); + MeasurementDao dao = new MeasurementDao(dsl); + List measurements = dao.retrieveMeasurements(officeId, locationId, minDate, maxDate, unitSystem, + minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agencies, qualities); + String formatHeader = ctx.header(Header.ACCEPT); + ContentType contentType = Formats.parseHeader(formatHeader, Measurement.class); + ctx.contentType(contentType.toString()); + String serialized = Formats.format(contentType, measurements, Measurement.class); + ctx.result(serialized); + ctx.status(HttpServletResponse.SC_OK); + requestResultSize.update(serialized.length()); + } + } + + @OpenApi(ignore = true) + @Override + public void getOne(@NotNull Context ctx, @NotNull String locationId) { + try (final Timer.Context ignored = markAndTime(GET_ONE)) { + throw new UnsupportedOperationException(NOT_SUPPORTED_YET); + } + + } + + @OpenApi( + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(isArray = true, from = Measurement.class, type = Formats.JSONV1), + @OpenApiContent(from = Measurement.class, type = Formats.JSON) + }, + required = true), + queryParams = { + @OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class, + description = "Create will fail if provided Measurement(s) already exist. Default: true") + }, + description = "Create new measurement(s).", + method = HttpMethod.POST, + tags = {TAG}, + responses = { + @OpenApiResponse(status = "204", description = "Measurement(s) successfully stored.") + } + ) + @Override + public void create(Context ctx) { + + try (Timer.Context ignored = markAndTime(CREATE)) { + String formatHeader = ctx.req.getContentType(); + ContentType contentType = Formats.parseHeader(formatHeader, Measurement.class); + List measurements = parseMeasurements(ctx, contentType); + boolean failIfExists = ctx.queryParamAsClass(FAIL_IF_EXISTS, Boolean.class).getOrDefault(true); + DSLContext dsl = getDslContext(ctx); + MeasurementDao dao = new MeasurementDao(dsl); + if(measurements.size() == 1) { + dao.storeMeasurement(measurements.get(0), failIfExists); + ctx.status(HttpServletResponse.SC_CREATED).json("Created Measurement"); + } else { + dao.storeMeasurements(measurements, failIfExists); + ctx.status(HttpServletResponse.SC_CREATED).json("Created Measurements"); + } + } + } + + @OpenApi(ignore = true) + @Override + public void update(@NotNull Context ctx, @NotNull String locationId) { + try (final Timer.Context ignored = markAndTime(GET_ONE)) { + throw new UnsupportedOperationException("Not supported with required location Id"); + } + } + + @OpenApi( + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the office of the measurements to delete"), + @OpenApiParam(name = MIN_NUMBER, description = "Specifies the min number of the measurement to delete."), + @OpenApiParam(name = MAX_NUMBER, description = "Specifies the max number of the measurement to delete."), + @OpenApiParam(name = MIN_DATE, description = "Specifies the minimum date of the measurement to delete."), + @OpenApiParam(name = MAX_DATE, description = "Specifies the maximum date of the measurement to delete."), + }, + description = "Delete an existing measurement.", + method = HttpMethod.DELETE, + tags = {TAG}, + responses = { + @OpenApiResponse(status = "204", description = "Measurement successfully deleted."), + @OpenApiResponse(status = "404", description = "Measurement not found.") + } + ) + @Override + public void delete(@NotNull Context ctx, @NotNull String locationId) { + String officeId = requiredParam(ctx, OFFICE); + String minNum = ctx.queryParam(MIN_NUMBER); + String maxNum = ctx.queryParam(MAX_NUMBER); + Instant minDate = parseInstant(ctx.queryParam(MIN_DATE)); + Instant maxDate = parseInstant(ctx.queryParam(MAX_DATE)); + try (Timer.Context ignored = markAndTime(DELETE)) { + DSLContext dsl = getDslContext(ctx); + MeasurementDao dao = new MeasurementDao(dsl); + dao.deleteMeasurements(officeId, locationId, minDate, maxDate, UnitSystem.EN.getValue(), null, + null, null, null, minNum, maxNum, null, null); + ctx.status(HttpServletResponse.SC_NO_CONTENT).json( "Measurements for " + locationId + " Deleted"); + } + } + + private Instant parseInstant(String date) { + Instant retVal = null; + if(date != null && !date.isEmpty()) { + retVal = Instant.parse(date); + } + return retVal; + } + + static List parseMeasurements(@NotNull Context ctx, ContentType contentType) { + List measurements; + try { + measurements = Formats.parseContentList(contentType, ctx.body(), Measurement.class); + } catch (FormattingException e) { + Measurement measurement = Formats.parseContent(contentType, ctx.body(), Measurement.class); + measurements = new ArrayList<>(); + measurements.add(measurement); + } + return measurements; + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java new file mode 100644 index 000000000..d5c311e14 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java @@ -0,0 +1,89 @@ +/* + * MIT License + * + * Copyright (c) 2024 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package cwms.cda.api; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import static cwms.cda.api.Controllers.STATUS_204; +import static cwms.cda.data.dao.JooqDao.getDslContext; +import cwms.cda.data.dao.MeasurementDao; +import cwms.cda.data.dto.measurement.Measurement; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +import io.javalin.http.Context; +import io.javalin.http.Handler; +import io.javalin.plugin.openapi.annotations.HttpMethod; +import io.javalin.plugin.openapi.annotations.OpenApi; +import io.javalin.plugin.openapi.annotations.OpenApiContent; +import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; +import io.javalin.plugin.openapi.annotations.OpenApiResponse; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; + +public final class MeasurementPatchController implements Handler { + + private final MetricRegistry metrics; + + public MeasurementPatchController(MetricRegistry metrics) { + this.metrics = metrics; + } + + @OpenApi( + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(isArray = true, from = Measurement.class, type = Formats.JSONV1), + @OpenApiContent(from = Measurement.class, type = Formats.JSON) + }, + required = true), + description = "Update Measurement", + method = HttpMethod.PATCH, + tags = {MeasurementController.TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "Measurement(s) successfully updated.") + } + ) + @Override + public void handle(@NotNull Context ctx) throws Exception { + try (Timer.Context ignored = markAndTime()) { + String formatHeader = ctx.req.getContentType(); + ContentType contentType = Formats.parseHeader(formatHeader, Measurement.class); + DSLContext dsl = getDslContext(ctx); + MeasurementDao dao = new MeasurementDao(dsl); + List measurements = MeasurementController.parseMeasurements(ctx, contentType); + if(measurements.size() == 1) { + dao.updateMeasurement(measurements.get(0)); + ctx.status(HttpServletResponse.SC_OK).json("Updated Measurement"); + } else { + dao.updateMeasurements(measurements); + ctx.status(HttpServletResponse.SC_OK).json("Updated Measurements"); + } + } + } + + private Timer.Context markAndTime() { + return Controllers.markAndTime(metrics, getClass().getName(), Controllers.UPDATE); + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/Measurement.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/Measurement.java index 1df9a1675..491b035b9 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/Measurement.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/Measurement.java @@ -23,6 +23,7 @@ */ package cwms.cda.data.dto.measurement; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -128,6 +129,7 @@ public CwmsId getId() { return id; } + @JsonFormat(shape = JsonFormat.Shape.STRING) public Instant getInstant() { return instant; } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java new file mode 100644 index 000000000..0919032f3 --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -0,0 +1,630 @@ +/* + * MIT License + * + * Copyright (c) 2024 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package cwms.cda.api; + +import cwms.cda.api.enums.UnitSystem; +import static cwms.cda.data.dao.DaoTest.getDslContext; +import cwms.cda.data.dao.DeleteRule; +import cwms.cda.data.dao.StreamDao; +import cwms.cda.data.dto.CwmsId; +import cwms.cda.data.dto.measurement.Measurement; +import cwms.cda.data.dto.stream.Stream; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +import static cwms.cda.security.KeyAccessManager.AUTH_HEADER; +import fixtures.CwmsDataApiSetupCallback; +import fixtures.TestAccounts; +import static io.restassured.RestAssured.given; +import io.restassured.filter.log.LogDetail; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.time.Instant; +import java.util.ArrayList; +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.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import static org.hamcrest.Matchers.*; + +@Tag("integration") +final class MeasurementControllerTestIT extends DataApiTestIT { + + private static final String OFFICE_ID = TestAccounts.KeyUser.SPK_NORMAL.getOperatingOffice(); + private static final List STREAMS_CREATED = new ArrayList<>(); + + @BeforeAll + public static void setup() throws SQLException { + String testLoc = "StreamLoc321"; // match the stream location name in the json file + createLocation(testLoc, true, OFFICE_ID, "STREAM_LOCATION"); + createAndStoreTestStream("ImOnThisStream2"); + } + + static void createAndStoreTestStream(String testLoc) throws SQLException { + createLocation(testLoc, true, OFFICE_ID, "STREAM"); + CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); + db.connection(c -> { + StreamDao streamDao = new StreamDao(getDslContext(c, OFFICE_ID)); + Stream streamToStore = new Stream.Builder() + .withId(new CwmsId.Builder() + .withOfficeId(OFFICE_ID) + .withName(testLoc) + .build()) + .withLength(100.0) + .withLengthUnits("km") + .build(); + STREAMS_CREATED.add(streamToStore); + streamDao.storeStream(streamToStore, false); + }, CwmsDataApiSetupCallback.getWebUser()); + } + + @AfterAll + public static void tearDown() { + for (Stream stream : STREAMS_CREATED) { + try { + CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); + db.connection(c -> { + StreamDao streamDao = new StreamDao(getDslContext(c, OFFICE_ID)); + try { + streamDao.deleteStream(stream.getId().getOfficeId(), stream.getId().getName(), DeleteRule.DELETE_ALL); + } catch (Exception e) { + // ignore + } + }, CwmsDataApiSetupCallback.getWebUser()); + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + } + STREAMS_CREATED.clear(); + } + + @Test + void test_create_retrieve_delete_measurement() throws IOException { + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurement.json"); + assertNotNull(resource); + String json = IOUtils.toString(resource, StandardCharsets.UTF_8); + assertNotNull(json); + Measurement measurement = Formats.parseContent(new ContentType(Formats.JSON), json, Measurement.class); + + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + + // Create the Measurement + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(json) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + + String locationId = measurement.getLocationId(); + String number = measurement.getNumber(); + + // Retrieve the Measurement and assert that it exists + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE_MASK, measurement.getId().getOfficeId()) + .queryParam(Controllers.ID_MASK, measurement.getLocationId()) + .queryParam(Controllers.MIN_NUMBER, number) + .queryParam(Controllers.MAX_NUMBER, number) + .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("[0].height-unit", equalTo(measurement.getHeightUnit())) + .body("[0].flow-unit", equalTo(measurement.getFlowUnit())) + .body("[0].temp-unit", equalTo(measurement.getTempUnit())) + .body("[0].velocity-unit", equalTo(measurement.getVelocityUnit())) + .body("[0].area-unit", equalTo(measurement.getAreaUnit())) + .body("[0].used", equalTo(measurement.isUsed())) + .body("[0].agency", equalTo(measurement.getAgency())) + .body("[0].party", equalTo(measurement.getParty())) + .body("[0].wm-comments", equalTo(measurement.getWmComments())) + .body("[0].instant", equalTo(measurement.getInstant().toString())) + .body("[0].number", equalTo(measurement.getNumber())) + .body("[0].id.name", equalTo(measurement.getLocationId())) + .body("[0].id.office-id", equalTo(measurement.getOfficeId())) + .body("[0].streamflow-measurement.gage-height", equalTo(measurement.getStreamflowMeasurement().getGageHeight().floatValue())) + .body("[0].streamflow-measurement.flow", equalTo(measurement.getStreamflowMeasurement().getFlow().floatValue())) + .body("[0].streamflow-measurement.quality", equalTo(measurement.getStreamflowMeasurement().getQuality())) + .body("[0].supplemental-streamflow-measurement.channel-flow", equalTo(measurement.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-flow", equalTo(measurement.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-max-depth", equalTo(measurement.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) + .body("[0].supplemental-streamflow-measurement.channel-max-depth", equalTo(measurement.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) + .body("[0].supplemental-streamflow-measurement.avg-velocity", equalTo(measurement.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.surface-velocity", equalTo(measurement.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.max-velocity", equalTo(measurement.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.effective-flow-area", equalTo(measurement.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.cross-sectional-area", equalTo(measurement.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.mean-gage", equalTo(measurement.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) + .body("[0].supplemental-streamflow-measurement.top-width", equalTo(measurement.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) + .body("[0].supplemental-streamflow-measurement.main-channel-area", equalTo(measurement.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-area", equalTo(measurement.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) + .body("[0].usgs-measurement.remarks", equalTo(measurement.getUsgsMeasurement().getRemarks())) + .body("[0].usgs-measurement.current-rating", equalTo(measurement.getUsgsMeasurement().getCurrentRating())) + .body("[0].usgs-measurement.control-condition", equalTo(measurement.getUsgsMeasurement().getControlCondition())) + .body("[0].usgs-measurement.flow-adjustment", equalTo(measurement.getUsgsMeasurement().getFlowAdjustment())) + .body("[0].usgs-measurement.shift-used", equalTo(measurement.getUsgsMeasurement().getShiftUsed().floatValue())) + .body("[0].usgs-measurement.percent-difference", equalTo(measurement.getUsgsMeasurement().getPercentDifference().floatValue())) + .body("[0].usgs-measurement.delta-height", equalTo(measurement.getUsgsMeasurement().getDeltaHeight().floatValue())) + .body("[0].usgs-measurement.delta-time", equalTo(measurement.getUsgsMeasurement().getDeltaTime().floatValue())) + .body("[0].usgs-measurement.air-temp", equalTo(measurement.getUsgsMeasurement().getAirTemp().floatValue())) + .body("[0].usgs-measurement.water-temp", equalTo(measurement.getUsgsMeasurement().getWaterTemp().floatValue())); + + + InputStream resourceUpdated = this.getClass().getResourceAsStream("/cwms/cda/api/measurement_updated.json"); + assertNotNull(resourceUpdated); + String jsonUpdated = IOUtils.toString(resourceUpdated, StandardCharsets.UTF_8); + assertNotNull(jsonUpdated); + Measurement updatedMeasurement = Formats.parseContent(new ContentType(Formats.JSON), jsonUpdated, Measurement.class); + + //Update the Measurement + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(jsonUpdated) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .patch("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + // Retrieve the Updated Measurement and assert that it exists with updated values + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE_MASK, updatedMeasurement.getId().getOfficeId()) + .queryParam(Controllers.ID_MASK, updatedMeasurement.getLocationId()) + .queryParam(Controllers.MIN_NUMBER, number) + .queryParam(Controllers.MAX_NUMBER, number) + .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("[0].height-unit", equalTo(updatedMeasurement.getHeightUnit())) + .body("[0].flow-unit", equalTo(updatedMeasurement.getFlowUnit())) + .body("[0].temp-unit", equalTo(updatedMeasurement.getTempUnit())) + .body("[0].velocity-unit", equalTo(updatedMeasurement.getVelocityUnit())) + .body("[0].area-unit", equalTo(updatedMeasurement.getAreaUnit())) + .body("[0].used", equalTo(updatedMeasurement.isUsed())) + .body("[0].agency", equalTo(updatedMeasurement.getAgency())) + .body("[0].party", equalTo(updatedMeasurement.getParty())) + .body("[0].wm-comments", equalTo(updatedMeasurement.getWmComments())) + .body("[0].instant", equalTo(updatedMeasurement.getInstant().toString())) + .body("[0].number", equalTo(updatedMeasurement.getNumber())) + .body("[0].id.name", equalTo(updatedMeasurement.getLocationId())) + .body("[0].id.office-id", equalTo(updatedMeasurement.getOfficeId())) + .body("[0].streamflow-measurement.gage-height", equalTo(updatedMeasurement.getStreamflowMeasurement().getGageHeight().floatValue())) + .body("[0].streamflow-measurement.flow", equalTo(updatedMeasurement.getStreamflowMeasurement().getFlow().floatValue())) + .body("[0].streamflow-measurement.quality", equalTo(updatedMeasurement.getStreamflowMeasurement().getQuality())) + .body("[0].supplemental-streamflow-measurement.channel-flow", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-flow", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-max-depth", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) + .body("[0].supplemental-streamflow-measurement.channel-max-depth", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) + .body("[0].supplemental-streamflow-measurement.avg-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.surface-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.max-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.effective-flow-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.cross-sectional-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.mean-gage", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) + .body("[0].supplemental-streamflow-measurement.top-width", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) + .body("[0].supplemental-streamflow-measurement.main-channel-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) + .body("[0].usgs-measurement.remarks", equalTo(updatedMeasurement.getUsgsMeasurement().getRemarks())) + .body("[0].usgs-measurement.current-rating", equalTo(updatedMeasurement.getUsgsMeasurement().getCurrentRating())) + .body("[0].usgs-measurement.control-condition", equalTo(updatedMeasurement.getUsgsMeasurement().getControlCondition())) + .body("[0].usgs-measurement.flow-adjustment", equalTo(updatedMeasurement.getUsgsMeasurement().getFlowAdjustment())) + .body("[0].usgs-measurement.shift-used", equalTo(updatedMeasurement.getUsgsMeasurement().getShiftUsed().floatValue())) + .body("[0].usgs-measurement.percent-difference", equalTo(updatedMeasurement.getUsgsMeasurement().getPercentDifference().floatValue())) + .body("[0].usgs-measurement.delta-height", equalTo(updatedMeasurement.getUsgsMeasurement().getDeltaHeight().floatValue())) + .body("[0].usgs-measurement.delta-time", equalTo(updatedMeasurement.getUsgsMeasurement().getDeltaTime().floatValue())) + .body("[0].usgs-measurement.air-temp", equalTo(updatedMeasurement.getUsgsMeasurement().getAirTemp().floatValue())) + .body("[0].usgs-measurement.water-temp", equalTo(updatedMeasurement.getUsgsMeasurement().getWaterTemp().floatValue())); + + // Delete the Measurement + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .header(AUTH_HEADER, user.toHeaderValue()) + .queryParam(Controllers.OFFICE, measurement.getId().getOfficeId()) + .queryParam(Controllers.MIN_NUMBER, number) + .queryParam(Controllers.MAX_NUMBER, number) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("/measurements/" + locationId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)); + + // Retrieve the Measurement and assert that it does not exist + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, measurement.getId().getOfficeId()) + .queryParam(Controllers.ID_MASK, measurement.getLocationId()) + .queryParam(Controllers.MIN_NUMBER, number) + .queryParam(Controllers.MAX_NUMBER, number) + .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); + } + + @Test + void test_create_retrieve_delete_measurement_multiple() throws IOException { + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurements.json"); + assertNotNull(resource); + String json = IOUtils.toString(resource, StandardCharsets.UTF_8); + assertNotNull(json); + List measurements = Formats.parseContentList(new ContentType(Formats.JSON), json, Measurement.class); + + Measurement measurement1 = measurements.get(0); + Measurement measurement2 = measurements.get(1); + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + + // Create the Measurements + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(json) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + + // Retrieve the Measurements and assert that they exists + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE_MASK, measurement1.getId().getOfficeId()) + .queryParam(Controllers.ID_MASK, measurement1.getLocationId()) + .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("[0].height-unit", equalTo(measurement1.getHeightUnit())) + .body("[0].flow-unit", equalTo(measurement1.getFlowUnit())) + .body("[0].temp-unit", equalTo(measurement1.getTempUnit())) + .body("[0].velocity-unit", equalTo(measurement1.getVelocityUnit())) + .body("[0].area-unit", equalTo(measurement1.getAreaUnit())) + .body("[0].used", equalTo(measurement1.isUsed())) + .body("[0].agency", equalTo(measurement1.getAgency())) + .body("[0].party", equalTo(measurement1.getParty())) + .body("[0].wm-comments", equalTo(measurement1.getWmComments())) + .body("[0].instant", equalTo(measurement1.getInstant().toString())) + .body("[0].number", equalTo(measurement1.getNumber())) + .body("[0].id.name", equalTo(measurement1.getLocationId())) + .body("[0].id.office-id", equalTo(measurement1.getOfficeId())) + .body("[0].streamflow-measurement.gage-height", equalTo(measurement1.getStreamflowMeasurement().getGageHeight().floatValue())) + .body("[0].streamflow-measurement.flow", equalTo(measurement1.getStreamflowMeasurement().getFlow().floatValue())) + .body("[0].streamflow-measurement.quality", equalTo(measurement1.getStreamflowMeasurement().getQuality())) + .body("[0].supplemental-streamflow-measurement.channel-flow", equalTo(measurement1.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-flow", equalTo(measurement1.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-max-depth", equalTo(measurement1.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) + .body("[0].supplemental-streamflow-measurement.channel-max-depth", equalTo(measurement1.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) + .body("[0].supplemental-streamflow-measurement.avg-velocity", equalTo(measurement1.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.surface-velocity", equalTo(measurement1.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.max-velocity", equalTo(measurement1.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.effective-flow-area", equalTo(measurement1.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.cross-sectional-area", equalTo(measurement1.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.mean-gage", equalTo(measurement1.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) + .body("[0].supplemental-streamflow-measurement.top-width", equalTo(measurement1.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) + .body("[0].supplemental-streamflow-measurement.main-channel-area", equalTo(measurement1.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-area", equalTo(measurement1.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) + .body("[0].usgs-measurement.remarks", equalTo(measurement1.getUsgsMeasurement().getRemarks())) + .body("[0].usgs-measurement.current-rating", equalTo(measurement1.getUsgsMeasurement().getCurrentRating())) + .body("[0].usgs-measurement.control-condition", equalTo(measurement1.getUsgsMeasurement().getControlCondition())) + .body("[0].usgs-measurement.flow-adjustment", equalTo(measurement1.getUsgsMeasurement().getFlowAdjustment())) + .body("[0].usgs-measurement.shift-used", equalTo(measurement1.getUsgsMeasurement().getShiftUsed().floatValue())) + .body("[0].usgs-measurement.percent-difference", equalTo(measurement1.getUsgsMeasurement().getPercentDifference().floatValue())) + .body("[0].usgs-measurement.delta-height", equalTo(measurement1.getUsgsMeasurement().getDeltaHeight().floatValue())) + .body("[0].usgs-measurement.delta-time", equalTo(measurement1.getUsgsMeasurement().getDeltaTime().floatValue())) + .body("[0].usgs-measurement.air-temp", equalTo(measurement1.getUsgsMeasurement().getAirTemp().floatValue())) + .body("[0].usgs-measurement.water-temp", equalTo(measurement1.getUsgsMeasurement().getWaterTemp().floatValue())) + .body("[1].height-unit", equalTo(measurement2.getHeightUnit())) + .body("[1].flow-unit", equalTo(measurement2.getFlowUnit())) + .body("[1].temp-unit", equalTo(measurement2.getTempUnit())) + .body("[1].velocity-unit", equalTo(measurement2.getVelocityUnit())) + .body("[1].area-unit", equalTo(measurement2.getAreaUnit())) + .body("[1].used", equalTo(measurement2.isUsed())) + .body("[1].agency", equalTo(measurement2.getAgency())) + .body("[1].party", equalTo(measurement2.getParty())) + .body("[1].wm-comments", equalTo(measurement2.getWmComments())) + .body("[1].instant", equalTo(measurement2.getInstant().toString())) + .body("[1].number", equalTo(measurement2.getNumber())) + .body("[1].id.name", equalTo(measurement2.getLocationId())) + .body("[1].id.office-id", equalTo(measurement2.getOfficeId())) + .body("[1].streamflow-measurement.gage-height", equalTo(measurement2.getStreamflowMeasurement().getGageHeight().floatValue())) + .body("[1].streamflow-measurement.flow", equalTo(measurement2.getStreamflowMeasurement().getFlow().floatValue())) + .body("[1].streamflow-measurement.quality", equalTo(measurement2.getStreamflowMeasurement().getQuality())) + .body("[1].supplemental-streamflow-measurement.channel-flow", equalTo(measurement2.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) + .body("[1].supplemental-streamflow-measurement.overbank-flow", equalTo(measurement2.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) + .body("[1].supplemental-streamflow-measurement.overbank-max-depth", equalTo(measurement2.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) + .body("[1].supplemental-streamflow-measurement.channel-max-depth", equalTo(measurement2.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) + .body("[1].supplemental-streamflow-measurement.avg-velocity", equalTo(measurement2.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) + .body("[1].supplemental-streamflow-measurement.surface-velocity", equalTo(measurement2.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) + .body("[1].supplemental-streamflow-measurement.max-velocity", equalTo(measurement2.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) + .body("[1].supplemental-streamflow-measurement.effective-flow-area", equalTo(measurement2.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) + .body("[1].supplemental-streamflow-measurement.cross-sectional-area", equalTo(measurement2.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) + .body("[1].supplemental-streamflow-measurement.mean-gage", equalTo(measurement2.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) + .body("[1].supplemental-streamflow-measurement.top-width", equalTo(measurement2.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) + .body("[1].supplemental-streamflow-measurement.main-channel-area", equalTo(measurement2.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) + .body("[1].supplemental-streamflow-measurement.overbank-area", equalTo(measurement2.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) + .body("[1].usgs-measurement.remarks", equalTo(measurement2.getUsgsMeasurement().getRemarks())) + .body("[1].usgs-measurement.current-rating", equalTo(measurement2.getUsgsMeasurement().getCurrentRating())) + .body("[1].usgs-measurement.control-condition", equalTo(measurement2.getUsgsMeasurement().getControlCondition())) + .body("[1].usgs-measurement.flow-adjustment", equalTo(measurement2.getUsgsMeasurement().getFlowAdjustment())) + .body("[1].usgs-measurement.shift-used", equalTo(measurement2.getUsgsMeasurement().getShiftUsed().floatValue())) + .body("[1].usgs-measurement.percent-difference", equalTo(measurement2.getUsgsMeasurement().getPercentDifference().floatValue())) + .body("[1].usgs-measurement.delta-height", equalTo(measurement2.getUsgsMeasurement().getDeltaHeight().floatValue())) + .body("[1].usgs-measurement.delta-time", equalTo(measurement2.getUsgsMeasurement().getDeltaTime().floatValue())) + .body("[1].usgs-measurement.air-temp", equalTo(measurement2.getUsgsMeasurement().getAirTemp().floatValue())) + .body("[1].usgs-measurement.water-temp", equalTo(measurement2.getUsgsMeasurement().getWaterTemp().floatValue())); + + + InputStream resourceUpdated = this.getClass().getResourceAsStream("/cwms/cda/api/measurements_updated.json"); + assertNotNull(resourceUpdated); + String jsonUpdated = IOUtils.toString(resourceUpdated, StandardCharsets.UTF_8); + assertNotNull(jsonUpdated); + List measurementsUpdated = Formats.parseContentList(new ContentType(Formats.JSON), jsonUpdated, Measurement.class); + Measurement updatedMeasurement = measurementsUpdated.get(0); + Measurement updatedMeasurement2 = measurementsUpdated.get(1); + + + //Update the Measurement + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(jsonUpdated) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .patch("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + // Retrieve the Updated Measurements and assert that they exists with updated values + String locationId = updatedMeasurement.getLocationId(); + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE_MASK, OFFICE_ID) + .queryParam(Controllers.ID_MASK, locationId) + .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("[0].height-unit", equalTo(updatedMeasurement.getHeightUnit())) + .body("[0].flow-unit", equalTo(updatedMeasurement.getFlowUnit())) + .body("[0].temp-unit", equalTo(updatedMeasurement.getTempUnit())) + .body("[0].velocity-unit", equalTo(updatedMeasurement.getVelocityUnit())) + .body("[0].area-unit", equalTo(updatedMeasurement.getAreaUnit())) + .body("[0].used", equalTo(updatedMeasurement.isUsed())) + .body("[0].agency", equalTo(updatedMeasurement.getAgency())) + .body("[0].party", equalTo(updatedMeasurement.getParty())) + .body("[0].wm-comments", equalTo(updatedMeasurement.getWmComments())) + .body("[0].instant", equalTo(updatedMeasurement.getInstant().toString())) + .body("[0].number", equalTo(updatedMeasurement.getNumber())) + .body("[0].id.name", equalTo(updatedMeasurement.getLocationId())) + .body("[0].id.office-id", equalTo(updatedMeasurement.getOfficeId())) + .body("[0].streamflow-measurement.gage-height", equalTo(updatedMeasurement.getStreamflowMeasurement().getGageHeight().floatValue())) + .body("[0].streamflow-measurement.flow", equalTo(updatedMeasurement.getStreamflowMeasurement().getFlow().floatValue())) + .body("[0].streamflow-measurement.quality", equalTo(updatedMeasurement.getStreamflowMeasurement().getQuality())) + .body("[0].supplemental-streamflow-measurement.channel-flow", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-flow", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-max-depth", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) + .body("[0].supplemental-streamflow-measurement.channel-max-depth", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) + .body("[0].supplemental-streamflow-measurement.avg-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.surface-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.max-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) + .body("[0].supplemental-streamflow-measurement.effective-flow-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.cross-sectional-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.mean-gage", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) + .body("[0].supplemental-streamflow-measurement.top-width", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) + .body("[0].supplemental-streamflow-measurement.main-channel-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) + .body("[0].supplemental-streamflow-measurement.overbank-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) + .body("[0].usgs-measurement.remarks", equalTo(updatedMeasurement.getUsgsMeasurement().getRemarks())) + .body("[0].usgs-measurement.current-rating", equalTo(updatedMeasurement.getUsgsMeasurement().getCurrentRating())) + .body("[0].usgs-measurement.control-condition", equalTo(updatedMeasurement.getUsgsMeasurement().getControlCondition())) + .body("[0].usgs-measurement.flow-adjustment", equalTo(updatedMeasurement.getUsgsMeasurement().getFlowAdjustment())) + .body("[0].usgs-measurement.shift-used", equalTo(updatedMeasurement.getUsgsMeasurement().getShiftUsed().floatValue())) + .body("[0].usgs-measurement.percent-difference", equalTo(updatedMeasurement.getUsgsMeasurement().getPercentDifference().floatValue())) + .body("[0].usgs-measurement.delta-height", equalTo(updatedMeasurement.getUsgsMeasurement().getDeltaHeight().floatValue())) + .body("[0].usgs-measurement.delta-time", equalTo(updatedMeasurement.getUsgsMeasurement().getDeltaTime().floatValue())) + .body("[0].usgs-measurement.air-temp", equalTo(updatedMeasurement.getUsgsMeasurement().getAirTemp().floatValue())) + .body("[0].usgs-measurement.water-temp", equalTo(updatedMeasurement.getUsgsMeasurement().getWaterTemp().floatValue())) + .body("[1].height-unit", equalTo(updatedMeasurement2.getHeightUnit())) + .body("[1].flow-unit", equalTo(updatedMeasurement2.getFlowUnit())) + .body("[1].temp-unit", equalTo(updatedMeasurement2.getTempUnit())) + .body("[1].velocity-unit", equalTo(updatedMeasurement2.getVelocityUnit())) + .body("[1].area-unit", equalTo(updatedMeasurement2.getAreaUnit())) + .body("[1].used", equalTo(updatedMeasurement2.isUsed())) + .body("[1].agency", equalTo(updatedMeasurement2.getAgency())) + .body("[1].party", equalTo(updatedMeasurement2.getParty())) + .body("[1].wm-comments", equalTo(updatedMeasurement2.getWmComments())) + .body("[1].instant", equalTo(updatedMeasurement2.getInstant().toString())) + .body("[1].number", equalTo(updatedMeasurement2.getNumber())) + .body("[1].id.name", equalTo(updatedMeasurement2.getLocationId())) + .body("[1].id.office-id", equalTo(updatedMeasurement2.getOfficeId())) + .body("[1].streamflow-measurement.gage-height", equalTo(updatedMeasurement2.getStreamflowMeasurement().getGageHeight().floatValue())) + .body("[1].streamflow-measurement.flow", equalTo(updatedMeasurement2.getStreamflowMeasurement().getFlow().floatValue())) + .body("[1].streamflow-measurement.quality", equalTo(updatedMeasurement2.getStreamflowMeasurement().getQuality())) + .body("[1].supplemental-streamflow-measurement.channel-flow", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) + .body("[1].supplemental-streamflow-measurement.overbank-flow", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) + .body("[1].supplemental-streamflow-measurement.overbank-max-depth", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) + .body("[1].supplemental-streamflow-measurement.channel-max-depth", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) + .body("[1].supplemental-streamflow-measurement.avg-velocity", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) + .body("[1].supplemental-streamflow-measurement.surface-velocity", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) + .body("[1].supplemental-streamflow-measurement.max-velocity", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) + .body("[1].supplemental-streamflow-measurement.effective-flow-area", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) + .body("[1].supplemental-streamflow-measurement.cross-sectional-area", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) + .body("[1].supplemental-streamflow-measurement.mean-gage", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) + .body("[1].supplemental-streamflow-measurement.top-width", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) + .body("[1].supplemental-streamflow-measurement.main-channel-area", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) + .body("[1].supplemental-streamflow-measurement.overbank-area", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) + .body("[1].usgs-measurement.remarks", equalTo(updatedMeasurement2.getUsgsMeasurement().getRemarks())) + .body("[1].usgs-measurement.current-rating", equalTo(updatedMeasurement2.getUsgsMeasurement().getCurrentRating())) + .body("[1].usgs-measurement.control-condition", equalTo(updatedMeasurement2.getUsgsMeasurement().getControlCondition())) + .body("[1].usgs-measurement.flow-adjustment", equalTo(updatedMeasurement2.getUsgsMeasurement().getFlowAdjustment())) + .body("[1].usgs-measurement.shift-used", equalTo(updatedMeasurement2.getUsgsMeasurement().getShiftUsed().floatValue())) + .body("[1].usgs-measurement.percent-difference", equalTo(updatedMeasurement2.getUsgsMeasurement().getPercentDifference().floatValue())) + .body("[1].usgs-measurement.delta-height", equalTo(updatedMeasurement2.getUsgsMeasurement().getDeltaHeight().floatValue())) + .body("[1].usgs-measurement.delta-time", equalTo(updatedMeasurement2.getUsgsMeasurement().getDeltaTime().floatValue())) + .body("[1].usgs-measurement.air-temp", equalTo(updatedMeasurement2.getUsgsMeasurement().getAirTemp().floatValue())) + .body("[1].usgs-measurement.water-temp", equalTo(updatedMeasurement2.getUsgsMeasurement().getWaterTemp().floatValue())); + + // Delete the Measurements + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .header(AUTH_HEADER, user.toHeaderValue()) + .queryParam(Controllers.OFFICE, OFFICE_ID) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("/measurements/" + locationId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)); + + // Retrieve the Measurements and assert that they do not exist + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, OFFICE_ID) + .queryParam(Controllers.ID_MASK, locationId) + .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); + } + + @Test + void test_update_does_not_exist() throws Exception { + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + + InputStream resourceUpdated = this.getClass().getResourceAsStream("/cwms/cda/api/measurements_updated.json"); + assertNotNull(resourceUpdated); + String jsonUpdated = IOUtils.toString(resourceUpdated, StandardCharsets.UTF_8); + assertNotNull(jsonUpdated); + List measurementsUpdated = Formats.parseContentList(new ContentType(Formats.JSON), jsonUpdated, Measurement.class); + + //Update the Measurement(s) that were never stored, and therefore doesn't exist in the db + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(jsonUpdated) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .patch("/measurements/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); + } + + @Test + void test_delete_does_not_exist() { + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + // Delete a Embankment + given() + .log().ifValidationFails(LogDetail.ALL,true) + .queryParam(Controllers.OFFICE, user.getOperatingOffice()) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("measurements/" + Instant.now().toEpochMilli()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); + } + +} diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/measurement.json b/cwms-data-api/src/test/resources/cwms/cda/api/measurement.json new file mode 100644 index 000000000..99f01da9e --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/measurement.json @@ -0,0 +1,49 @@ +{ + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made during normal flow conditions.", + "instant": "2024-09-16T00:00:00Z", + "number": "123456", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 5.5, + "flow": 250.0, + "quality": "G" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 300.0, + "overbank-flow": 50.0, + "overbank-max-depth": 5.0, + "channel-max-depth": 10.0, + "avg-velocity": 1.5, + "surface-velocity": 2.0, + "max-velocity": 3.0, + "effective-flow-area": 200.0, + "cross-sectional-area": 250.0, + "mean-gage": 20.0, + "top-width": 30.0, + "main-channel-area": 150.0, + "overbank-area": 80.0 + }, + "usgs-measurement": { + "remarks": "Remarks", + "current-rating": "1", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.1, + "percent-difference": 5.0, + "delta-height": 0.05, + "delta-time": 10.0, + "air-temp": 20.0, + "water-temp": 15.0 + } +} diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/measurement_updated.json b/cwms-data-api/src/test/resources/cwms/cda/api/measurement_updated.json new file mode 100644 index 000000000..e137289df --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/measurement_updated.json @@ -0,0 +1,49 @@ +{ + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made during normal flow conditions.", + "instant": "2024-09-16T00:00:00Z", + "number": "123456", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 5.8, + "flow": 350.0, + "quality": "G" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 300.0, + "overbank-flow": 50.0, + "overbank-max-depth": 5.0, + "channel-max-depth": 10.0, + "avg-velocity": 1.5, + "surface-velocity": 2.0, + "max-velocity": 3.0, + "effective-flow-area": 200.0, + "cross-sectional-area": 250.0, + "mean-gage": 20.0, + "top-width": 30.0, + "main-channel-area": 150.0, + "overbank-area": 80.0 + }, + "usgs-measurement": { + "remarks": "Remarks", + "current-rating": "1", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.1, + "percent-difference": 5.0, + "delta-height": 0.05, + "delta-time": 10.0, + "air-temp": 20.0, + "water-temp": 15.0 + } +} diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/measurements.json b/cwms-data-api/src/test/resources/cwms/cda/api/measurements.json new file mode 100644 index 000000000..3db3ce30b --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/measurements.json @@ -0,0 +1,100 @@ +[ + { + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made during normal flow conditions.", + "instant": "2024-09-16T00:00:00Z", + "number": "123456", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 5.5, + "flow": 250.0, + "quality": "G" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 300.0, + "overbank-flow": 50.0, + "overbank-max-depth": 5.0, + "channel-max-depth": 10.0, + "avg-velocity": 1.5, + "surface-velocity": 2.0, + "max-velocity": 3.0, + "effective-flow-area": 200.0, + "cross-sectional-area": 250.0, + "mean-gage": 20.0, + "top-width": 30.0, + "main-channel-area": 150.0, + "overbank-area": 80.0 + }, + "usgs-measurement": { + "remarks": "Remarks", + "current-rating": "1", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.1, + "percent-difference": 5.0, + "delta-height": 0.05, + "delta-time": 10.0, + "air-temp": 20.0, + "water-temp": 15.0 + } + }, + { + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made after recent rainfall.", + "instant": "2024-09-17T12:00:00Z", + "number": "654321", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 6.0, + "flow": 275.0, + "quality": "F" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 320.0, + "overbank-flow": 45.0, + "overbank-max-depth": 4.5, + "channel-max-depth": 9.5, + "avg-velocity": 1.8, + "surface-velocity": 2.5, + "max-velocity": 3.5, + "effective-flow-area": 220.0, + "cross-sectional-area": 260.0, + "mean-gage": 21.0, + "top-width": 32.0, + "main-channel-area": 160.0, + "overbank-area": 85.0 + }, + "usgs-measurement": { + "remarks": "Post-rain conditions.", + "current-rating": "2", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.15, + "percent-difference": 4.5, + "delta-height": 0.1, + "delta-time": 15.0, + "air-temp": 18.0, + "water-temp": 16.0 + } + } +] diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/measurements_updated.json b/cwms-data-api/src/test/resources/cwms/cda/api/measurements_updated.json new file mode 100644 index 000000000..e681d6e73 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/measurements_updated.json @@ -0,0 +1,100 @@ +[ + { + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made during normal flow conditions.", + "instant": "2024-09-16T00:00:00Z", + "number": "123456", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 5.8, + "flow": 350.0, + "quality": "G" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 300.0, + "overbank-flow": 50.0, + "overbank-max-depth": 5.0, + "channel-max-depth": 10.0, + "avg-velocity": 1.5, + "surface-velocity": 2.0, + "max-velocity": 3.0, + "effective-flow-area": 200.0, + "cross-sectional-area": 250.0, + "mean-gage": 20.0, + "top-width": 30.0, + "main-channel-area": 150.0, + "overbank-area": 80.0 + }, + "usgs-measurement": { + "remarks": "Remarks", + "current-rating": "1", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.1, + "percent-difference": 5.0, + "delta-height": 0.05, + "delta-time": 10.0, + "air-temp": 20.0, + "water-temp": 15.0 + } + }, + { + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made after recent rainfall2.", + "instant": "2024-09-17T12:00:00Z", + "number": "654321", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 6.4, + "flow": 279.0, + "quality": "F" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 320.0, + "overbank-flow": 45.0, + "overbank-max-depth": 4.5, + "channel-max-depth": 9.8, + "avg-velocity": 1.8, + "surface-velocity": 2.5, + "max-velocity": 3.5, + "effective-flow-area": 220.0, + "cross-sectional-area": 240.0, + "mean-gage": 21.0, + "top-width": 32.0, + "main-channel-area": 163.0, + "overbank-area": 82.0 + }, + "usgs-measurement": { + "remarks": "Post-rain conditions.", + "current-rating": "2", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.15, + "percent-difference": 4.5, + "delta-height": 0.4, + "delta-time": 15.0, + "air-temp": 19.0, + "water-temp": 16.0 + } + } +] From d6ddbcf134017526acf1227dc87014ae4260d6be Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Fri, 11 Oct 2024 15:37:30 -0700 Subject: [PATCH 02/10] CWDB-225 - Updated some descriptions for clarity. Added deletion of measurements to cleanup routine in IT. Code format refactors. --- .../src/main/java/cwms/cda/ApiServlet.java | 2 +- .../cwms/cda/api/MeasurementController.java | 20 ++++++------- .../cda/api/MeasurementPatchController.java | 2 +- .../cda/api/MeasurementControllerTestIT.java | 28 +++++++++++++++++-- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java index e82b59349..a82856116 100644 --- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java @@ -542,7 +542,7 @@ protected void configureRoutes() { cdaCrudCache(format("/stream-reaches/{%s}", NAME), new StreamReachController(metrics), requiredRoles,1, TimeUnit.DAYS); String measurements = "/measurements/"; - patch(measurements ,new MeasurementPatchController(metrics)); + patch(measurements, new MeasurementPatchController(metrics)); cdaCrudCache(format(measurements + "{%s}", LOCATION_ID), new cwms.cda.api.MeasurementController(metrics), requiredRoles,5, TimeUnit.MINUTES); cdaCrudCache("/blobs/{blob-id}", diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java index 377daa3b3..24e8cfad4 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java @@ -86,12 +86,12 @@ private Timer.Context markAndTime(String subject) { @OpenApi( queryParams = { - @OpenApiParam(name = OFFICE_MASK, description = "Office id for filtering measurements."), - @OpenApiParam(name = ID_MASK, description = "Location id for filtering measurements."), - @OpenApiParam(name = MIN_NUMBER, description = "Minimum number for filtering measurements."), - @OpenApiParam(name = MAX_NUMBER, description = "Maximum number for filtering measurements."), - @OpenApiParam(name = MIN_DATE, description = "Minimum date for filtering measurements."), - @OpenApiParam(name = MAX_DATE, description = "Maximum date for filtering measurements."), + @OpenApiParam(name = OFFICE_MASK, description = "Office id mask for filtering measurements. Use null to retrieve measurements for all offices."), + @OpenApiParam(name = ID_MASK, description = "Location id mask for filtering measurements. Use null to retrieve measurements for all locations."), + @OpenApiParam(name = MIN_NUMBER, description = "Minimum measurement number-id for filtering measurements."), + @OpenApiParam(name = MAX_NUMBER, description = "Maximum measurement number-id for filtering measurements."), + @OpenApiParam(name = MIN_DATE, description = "Minimum date for filtering measurements in ISO-8601 format."), + @OpenApiParam(name = MAX_DATE, description = "Maximum date for filtering measurements in ISO-8601 format."), @OpenApiParam(name = UNIT_SYSTEM, description = "Specifies the unit system" + " of the response. Valid values for the unit field are: " + "\n* `EN` Specifies English unit system. Location values will be in the " @@ -196,10 +196,10 @@ public void update(@NotNull Context ctx, @NotNull String locationId) { @OpenApi( queryParams = { @OpenApiParam(name = OFFICE, required = true, description = "Specifies the office of the measurements to delete"), - @OpenApiParam(name = MIN_NUMBER, description = "Specifies the min number of the measurement to delete."), - @OpenApiParam(name = MAX_NUMBER, description = "Specifies the max number of the measurement to delete."), - @OpenApiParam(name = MIN_DATE, description = "Specifies the minimum date of the measurement to delete."), - @OpenApiParam(name = MAX_DATE, description = "Specifies the maximum date of the measurement to delete."), + @OpenApiParam(name = MIN_NUMBER, description = "Specifies the min number-id of the measurement to delete."), + @OpenApiParam(name = MAX_NUMBER, description = "Specifies the max number-id of the measurement to delete."), + @OpenApiParam(name = MIN_DATE, description = "Specifies the minimum date (in ISO-8601 format) of the measurement to delete."), + @OpenApiParam(name = MAX_DATE, description = "Specifies the maximum date (in ISO-8601 format) of the measurement to delete."), }, description = "Delete an existing measurement.", method = HttpMethod.DELETE, diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java index d5c311e14..ea4bce279 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java @@ -58,7 +58,7 @@ public MeasurementPatchController(MetricRegistry metrics) { @OpenApiContent(from = Measurement.class, type = Formats.JSON) }, required = true), - description = "Update Measurement", + description = "Update Measurement Data. This is not a rename operation. The measurement data is updated with the new data provided.", method = HttpMethod.PATCH, tags = {MeasurementController.TAG}, responses = { diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index 0919032f3..d724a0af2 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -26,6 +26,7 @@ import cwms.cda.api.enums.UnitSystem; import static cwms.cda.data.dao.DaoTest.getDslContext; import cwms.cda.data.dao.DeleteRule; +import cwms.cda.data.dao.MeasurementDao; import cwms.cda.data.dao.StreamDao; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.measurement.Measurement; @@ -50,6 +51,7 @@ import org.junit.jupiter.api.AfterAll; import static org.junit.jupiter.api.Assertions.assertNotNull; 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 org.hamcrest.Matchers.*; @@ -59,11 +61,13 @@ final class MeasurementControllerTestIT extends DataApiTestIT { private static final String OFFICE_ID = TestAccounts.KeyUser.SPK_NORMAL.getOperatingOffice(); private static final List STREAMS_CREATED = new ArrayList<>(); + private static final List STREAM_LOC_IDS = new ArrayList<>(); @BeforeAll public static void setup() throws SQLException { String testLoc = "StreamLoc321"; // match the stream location name in the json file createLocation(testLoc, true, OFFICE_ID, "STREAM_LOCATION"); + STREAM_LOC_IDS.add(testLoc); createAndStoreTestStream("ImOnThisStream2"); } @@ -103,9 +107,26 @@ public static void tearDown() { } } STREAMS_CREATED.clear(); + for(String measLoc: STREAM_LOC_IDS) + { + try { + CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); + db.connection(c -> { + MeasurementDao measDao = new MeasurementDao(getDslContext(c, OFFICE_ID)); + try { + measDao.deleteMeasurements(OFFICE_ID, measLoc, null, null, null, null, null, null, null, null, null, null, null); + } catch (Exception e) { + // ignore + } + }, CwmsDataApiSetupCallback.getWebUser()); + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + } } @Test + @Disabled void test_create_retrieve_delete_measurement() throws IOException { InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurement.json"); assertNotNull(resource); @@ -149,7 +170,7 @@ void test_create_retrieve_delete_measurement() throws IOException { .get("/measurements/") .then() .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() + .assertThat() .statusCode(is(HttpServletResponse.SC_OK)) .body("[0].height-unit", equalTo(measurement.getHeightUnit())) .body("[0].flow-unit", equalTo(measurement.getFlowUnit())) @@ -308,6 +329,7 @@ void test_create_retrieve_delete_measurement() throws IOException { } @Test + @Disabled void test_create_retrieve_delete_measurement_multiple() throws IOException { InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurements.json"); assertNotNull(resource); @@ -583,6 +605,7 @@ void test_create_retrieve_delete_measurement_multiple() throws IOException { } @Test + @Disabled void test_update_does_not_exist() throws Exception { TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; @@ -610,9 +633,10 @@ void test_update_does_not_exist() throws Exception { } @Test + @Disabled void test_delete_does_not_exist() { TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; - // Delete a Embankment + // Delete a Measurement given() .log().ifValidationFails(LogDetail.ALL,true) .queryParam(Controllers.OFFICE, user.getOperatingOffice()) From 7edd50dcbefd7c85269fa2bb1bd2634ec8b657af Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Mon, 14 Oct 2024 12:48:23 -0700 Subject: [PATCH 03/10] CWDB-225 - Added logging to IT clean up process. --- .../cda/api/MeasurementControllerTestIT.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index d724a0af2..88dd7e187 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -23,6 +23,7 @@ */ package cwms.cda.api; +import com.google.common.flogger.FluentLogger; import cwms.cda.api.enums.UnitSystem; import static cwms.cda.data.dao.DaoTest.getDslContext; import cwms.cda.data.dao.DeleteRule; @@ -59,15 +60,17 @@ @Tag("integration") final class MeasurementControllerTestIT extends DataApiTestIT { + + private static final FluentLogger LOGGER = FluentLogger.forEnclosingClass(); private static final String OFFICE_ID = TestAccounts.KeyUser.SPK_NORMAL.getOperatingOffice(); - private static final List STREAMS_CREATED = new ArrayList<>(); - private static final List STREAM_LOC_IDS = new ArrayList<>(); + private static final List TEST_STREAMS = new ArrayList<>(); + private static final List TEST_STREAM_LOC_IDS = new ArrayList<>(); @BeforeAll public static void setup() throws SQLException { String testLoc = "StreamLoc321"; // match the stream location name in the json file createLocation(testLoc, true, OFFICE_ID, "STREAM_LOCATION"); - STREAM_LOC_IDS.add(testLoc); + TEST_STREAM_LOC_IDS.add(testLoc); createAndStoreTestStream("ImOnThisStream2"); } @@ -84,14 +87,14 @@ static void createAndStoreTestStream(String testLoc) throws SQLException { .withLength(100.0) .withLengthUnits("km") .build(); - STREAMS_CREATED.add(streamToStore); + TEST_STREAMS.add(streamToStore); streamDao.storeStream(streamToStore, false); }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll public static void tearDown() { - for (Stream stream : STREAMS_CREATED) { + for (Stream stream : TEST_STREAMS) { try { CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); db.connection(c -> { @@ -99,15 +102,15 @@ public static void tearDown() { try { streamDao.deleteStream(stream.getId().getOfficeId(), stream.getId().getName(), DeleteRule.DELETE_ALL); } catch (Exception e) { - // ignore + LOGGER.atInfo().log("Failed to delete stream: " + stream.getId().getName() + ". Stream likely already deleted"); } }, CwmsDataApiSetupCallback.getWebUser()); } catch (SQLException ex) { throw new RuntimeException(ex); } } - STREAMS_CREATED.clear(); - for(String measLoc: STREAM_LOC_IDS) + TEST_STREAMS.clear(); + for(String measLoc: TEST_STREAM_LOC_IDS) { try { CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); @@ -116,7 +119,7 @@ public static void tearDown() { try { measDao.deleteMeasurements(OFFICE_ID, measLoc, null, null, null, null, null, null, null, null, null, null, null); } catch (Exception e) { - // ignore + LOGGER.atInfo().log("Failed to delete measurements for: " + measLoc + ". Measurement(s) likely already deleted"); } }, CwmsDataApiSetupCallback.getWebUser()); } catch (SQLException ex) { From a84e771cbd5f617066bb0693b53135f2be504d57 Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Mon, 14 Oct 2024 13:24:06 -0700 Subject: [PATCH 04/10] CWDB-225 - Added message to disable tag --- .../java/cwms/cda/api/MeasurementControllerTestIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index 88dd7e187..d123ef391 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -129,7 +129,7 @@ public static void tearDown() { } @Test - @Disabled + @Disabled("Disabled until schema with updated measurement api is deployed as production schema.") void test_create_retrieve_delete_measurement() throws IOException { InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurement.json"); assertNotNull(resource); @@ -332,7 +332,7 @@ void test_create_retrieve_delete_measurement() throws IOException { } @Test - @Disabled + @Disabled("Disabled until schema with updated measurement api is deployed as production schema.") void test_create_retrieve_delete_measurement_multiple() throws IOException { InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurements.json"); assertNotNull(resource); @@ -608,7 +608,7 @@ void test_create_retrieve_delete_measurement_multiple() throws IOException { } @Test - @Disabled + @Disabled("Disabled until schema with updated measurement api is deployed as production schema.") void test_update_does_not_exist() throws Exception { TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; @@ -636,7 +636,7 @@ void test_update_does_not_exist() throws Exception { } @Test - @Disabled + @Disabled("Disabled until schema with updated measurement api is deployed as production schema.") void test_delete_does_not_exist() { TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; // Delete a Measurement From 561e3a06f71c95ca0ac2c3b48c546f2f392d8f3e Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Wed, 16 Oct 2024 12:56:47 -0700 Subject: [PATCH 05/10] CWDB-225 - Updated to use MinimumSchema tag for ITs --- .../java/cwms/cda/api/MeasurementControllerTestIT.java | 10 ++++++---- .../java/cwms/cda/data/dao/MeasurementDaoTestIT.java | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index d123ef391..ec79acd76 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -28,6 +28,7 @@ import static cwms.cda.data.dao.DaoTest.getDslContext; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.MeasurementDao; +import static cwms.cda.data.dao.MeasurementDaoTestIT.MINIMUM_SCHEMA; import cwms.cda.data.dao.StreamDao; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.measurement.Measurement; @@ -36,6 +37,7 @@ import cwms.cda.formatters.Formats; import static cwms.cda.security.KeyAccessManager.AUTH_HEADER; import fixtures.CwmsDataApiSetupCallback; +import fixtures.MinimumSchema; import fixtures.TestAccounts; import static io.restassured.RestAssured.given; import io.restassured.filter.log.LogDetail; @@ -129,7 +131,7 @@ public static void tearDown() { } @Test - @Disabled("Disabled until schema with updated measurement api is deployed as production schema.") + @MinimumSchema(MINIMUM_SCHEMA) void test_create_retrieve_delete_measurement() throws IOException { InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurement.json"); assertNotNull(resource); @@ -332,7 +334,7 @@ void test_create_retrieve_delete_measurement() throws IOException { } @Test - @Disabled("Disabled until schema with updated measurement api is deployed as production schema.") + @MinimumSchema(MINIMUM_SCHEMA) void test_create_retrieve_delete_measurement_multiple() throws IOException { InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurements.json"); assertNotNull(resource); @@ -608,7 +610,7 @@ void test_create_retrieve_delete_measurement_multiple() throws IOException { } @Test - @Disabled("Disabled until schema with updated measurement api is deployed as production schema.") + @MinimumSchema(MINIMUM_SCHEMA) void test_update_does_not_exist() throws Exception { TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; @@ -636,7 +638,7 @@ void test_update_does_not_exist() throws Exception { } @Test - @Disabled("Disabled until schema with updated measurement api is deployed as production schema.") + @MinimumSchema(MINIMUM_SCHEMA) void test_delete_does_not_exist() { TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; // Delete a Measurement diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java index b8fec3445..a967818a4 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java @@ -14,6 +14,7 @@ import cwms.cda.data.dto.stream.StreamLocation; import cwms.cda.helpers.DTOMatch; import fixtures.CwmsDataApiSetupCallback; +import fixtures.MinimumSchema; import fixtures.TestAccounts; import java.sql.SQLException; import java.time.Instant; @@ -32,11 +33,12 @@ import org.junit.jupiter.api.Test; @Tag("integration") -final class MeasurementDaoTestIT extends DataApiTestIT { +public final class MeasurementDaoTestIT extends DataApiTestIT { private static final String OFFICE_ID = TestAccounts.KeyUser.SPK_NORMAL.getOperatingOffice(); private static final List STREAM_LOC_IDS = new ArrayList<>(); private static final List STREAMS_CREATED = new ArrayList<>(); + public static final int MINIMUM_SCHEMA = 999999; @BeforeAll public static void setup() { @@ -79,7 +81,7 @@ public static void tearDown() { } @Test - @Disabled + @MinimumSchema(MINIMUM_SCHEMA) void testRoundTrip() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); String webUser = CwmsDataApiSetupCallback.getWebUser(); @@ -168,7 +170,7 @@ void testRoundTrip() throws Exception { } @Test - @Disabled + @MinimumSchema(MINIMUM_SCHEMA) void testRoundTripMultipleStore() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); String webUser = CwmsDataApiSetupCallback.getWebUser(); From 4f75f8753abee88897bcfac5076ec31e7813b8e0 Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Wed, 16 Oct 2024 17:15:50 -0700 Subject: [PATCH 06/10] CWDB-225 - Added a override property for checking for existing measurements for bulk update --- .../java/cwms/cda/data/dao/MeasurementDao.java | 15 +++++++++------ .../cwms/cda/api/MeasurementControllerTestIT.java | 3 +++ .../cwms/cda/data/dao/MeasurementDaoTestIT.java | 4 ++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java index 1d1b9e4ee..3dc762322 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java @@ -59,7 +59,6 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Comparator; import java.util.Date; import java.util.TimeZone; import java.util.stream.Collectors; @@ -78,6 +77,7 @@ public final class MeasurementDao extends JooqDao { static final XmlMapper XML_MAPPER = buildXmlMapper(); + public static final String IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY = "measurement.ignoreExistingCheckForBulkUpdate"; public MeasurementDao(DSLContext dsl) { super(dsl); @@ -251,11 +251,14 @@ private void verifyMeasurementsExists(Connection conn, String officeId, String l .collect(Collectors.toList()); // Retrieve existing measurements from the database - List existingNumbers = getExistingMeasurementNumbers(conn, officeId, locationId, measurementNumbers); - - // Find missing numbers - List missingNumbers = new ArrayList<>(measurementNumbers); - missingNumbers.removeAll(existingNumbers); + List missingNumbers = new ArrayList<>(); + if(!Boolean.getBoolean(IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY)) + { + List existingNumbers = getExistingMeasurementNumbers(conn, officeId, locationId, measurementNumbers); + missingNumbers = new ArrayList<>(measurementNumbers); + // Find missing numbers + missingNumbers.removeAll(existingNumbers); + } if (!missingNumbers.isEmpty()) { throw new NotFoundException("Could not find measurements " + String.join(",", missingNumbers) + diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index ec79acd76..ff18faae0 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -96,6 +96,7 @@ static void createAndStoreTestStream(String testLoc) throws SQLException { @AfterAll public static void tearDown() { + System.clearProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY); for (Stream stream : TEST_STREAMS) { try { CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); @@ -336,6 +337,7 @@ void test_create_retrieve_delete_measurement() throws IOException { @Test @MinimumSchema(MINIMUM_SCHEMA) void test_create_retrieve_delete_measurement_multiple() throws IOException { + System.setProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY, String.valueOf(true)); InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurements.json"); assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); @@ -612,6 +614,7 @@ void test_create_retrieve_delete_measurement_multiple() throws IOException { @Test @MinimumSchema(MINIMUM_SCHEMA) void test_update_does_not_exist() throws Exception { + System.clearProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY); TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; InputStream resourceUpdated = this.getClass().getResourceAsStream("/cwms/cda/api/measurements_updated.json"); diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java index a967818a4..ce1b66912 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java @@ -184,6 +184,7 @@ void testRoundTripMultipleStore() throws Exception { StreamLocation streamLocation2 = StreamLocationDaoTestIT.buildTestStreamLocation("TEST_STREAM_123", streamLocId2, OFFICE_ID,11.0, Bank.RIGHT); try { + System.setProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY, String.valueOf(true)); //store stream locations streamLocationDao.storeStreamLocation(streamLocation, false); streamLocationDao.storeStreamLocation(streamLocation2, false); @@ -231,6 +232,8 @@ void testRoundTripMultipleStore() throws Exception { updatedMeasurements.add(meas1B); measurementDao.updateMeasurements(updatedMeasurements); + System.clearProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY); + retrievedMeasurements = measurementDao.retrieveMeasurements(OFFICE_ID, streamLocId, null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null); @@ -264,6 +267,7 @@ void testRoundTripMultipleStore() throws Exception { assertThrows(NotFoundException.class, () -> measurementDao.retrieveMeasurements(meas2F.getId().getOfficeId(), meas2F.getId().getName(), null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null)); } finally { + System.clearProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY); //delete stream locations streamLocationDao.deleteStreamLocation( streamLocation.getStreamLocationNode().getId().getOfficeId(), From 87859c167796206f633e07a08ba90613c477998c Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Thu, 17 Oct 2024 09:06:15 -0700 Subject: [PATCH 07/10] CWDB-225 - Updated to de-support update/PATCH for measurements. --- .../src/main/java/cwms/cda/ApiServlet.java | 2 - .../cwms/cda/api/MeasurementController.java | 2 +- .../cda/api/MeasurementPatchController.java | 89 ------- .../cwms/cda/data/dao/MeasurementDao.java | 89 ------- .../cda/api/MeasurementControllerTestIT.java | 237 +----------------- .../cda/data/dao/MeasurementDaoTestIT.java | 28 --- .../cwms/cda/api/measurement_updated.json | 49 ---- .../cwms/cda/api/measurements_updated.json | 100 -------- 8 files changed, 3 insertions(+), 593 deletions(-) delete mode 100644 cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java delete mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/measurement_updated.json delete mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/measurements_updated.json diff --git a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java index a82856116..4082bc32c 100644 --- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java @@ -31,7 +31,6 @@ import static cwms.cda.api.Controllers.OFFICE; import static cwms.cda.api.Controllers.PROJECT_ID; import static cwms.cda.api.Controllers.WATER_USER; -import cwms.cda.api.MeasurementPatchController; import static io.javalin.apibuilder.ApiBuilder.crud; import static io.javalin.apibuilder.ApiBuilder.delete; import static io.javalin.apibuilder.ApiBuilder.get; @@ -542,7 +541,6 @@ protected void configureRoutes() { cdaCrudCache(format("/stream-reaches/{%s}", NAME), new StreamReachController(metrics), requiredRoles,1, TimeUnit.DAYS); String measurements = "/measurements/"; - patch(measurements, new MeasurementPatchController(metrics)); cdaCrudCache(format(measurements + "{%s}", LOCATION_ID), new cwms.cda.api.MeasurementController(metrics), requiredRoles,5, TimeUnit.MINUTES); cdaCrudCache("/blobs/{blob-id}", diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java index 24e8cfad4..72df06b33 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java @@ -189,7 +189,7 @@ public void create(Context ctx) { @Override public void update(@NotNull Context ctx, @NotNull String locationId) { try (final Timer.Context ignored = markAndTime(GET_ONE)) { - throw new UnsupportedOperationException("Not supported with required location Id"); + throw new UnsupportedOperationException(NOT_SUPPORTED_YET); } } diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java deleted file mode 100644 index ea4bce279..000000000 --- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementPatchController.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Hydrologic Engineering Center - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package cwms.cda.api; - -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.Timer; -import static cwms.cda.api.Controllers.STATUS_204; -import static cwms.cda.data.dao.JooqDao.getDslContext; -import cwms.cda.data.dao.MeasurementDao; -import cwms.cda.data.dto.measurement.Measurement; -import cwms.cda.formatters.ContentType; -import cwms.cda.formatters.Formats; -import io.javalin.http.Context; -import io.javalin.http.Handler; -import io.javalin.plugin.openapi.annotations.HttpMethod; -import io.javalin.plugin.openapi.annotations.OpenApi; -import io.javalin.plugin.openapi.annotations.OpenApiContent; -import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; -import io.javalin.plugin.openapi.annotations.OpenApiResponse; -import java.util.List; -import javax.servlet.http.HttpServletResponse; -import org.jetbrains.annotations.NotNull; -import org.jooq.DSLContext; - -public final class MeasurementPatchController implements Handler { - - private final MetricRegistry metrics; - - public MeasurementPatchController(MetricRegistry metrics) { - this.metrics = metrics; - } - - @OpenApi( - requestBody = @OpenApiRequestBody( - content = { - @OpenApiContent(isArray = true, from = Measurement.class, type = Formats.JSONV1), - @OpenApiContent(from = Measurement.class, type = Formats.JSON) - }, - required = true), - description = "Update Measurement Data. This is not a rename operation. The measurement data is updated with the new data provided.", - method = HttpMethod.PATCH, - tags = {MeasurementController.TAG}, - responses = { - @OpenApiResponse(status = STATUS_204, description = "Measurement(s) successfully updated.") - } - ) - @Override - public void handle(@NotNull Context ctx) throws Exception { - try (Timer.Context ignored = markAndTime()) { - String formatHeader = ctx.req.getContentType(); - ContentType contentType = Formats.parseHeader(formatHeader, Measurement.class); - DSLContext dsl = getDslContext(ctx); - MeasurementDao dao = new MeasurementDao(dsl); - List measurements = MeasurementController.parseMeasurements(ctx, contentType); - if(measurements.size() == 1) { - dao.updateMeasurement(measurements.get(0)); - ctx.status(HttpServletResponse.SC_OK).json("Updated Measurement"); - } else { - dao.updateMeasurements(measurements); - ctx.status(HttpServletResponse.SC_OK).json("Updated Measurements"); - } - } - } - - private Timer.Context markAndTime() { - return Controllers.markAndTime(metrics, getClass().getName(), Controllers.UPDATE); - } -} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java index 3dc762322..6be46ee21 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java @@ -61,7 +61,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.TimeZone; -import java.util.stream.Collectors; import mil.army.usace.hec.metadata.location.LocationTemplate; import org.jooq.DSLContext; @@ -71,13 +70,11 @@ import org.jooq.impl.DSL; import usace.cwms.db.dao.util.OracleTypeMap; import usace.cwms.db.jooq.codegen.packages.CWMS_STREAM_PACKAGE; -import static usace.cwms.db.jooq.codegen.tables.AV_STREAMFLOW_MEAS.AV_STREAMFLOW_MEAS; import usace.cwms.db.jooq.codegen.udt.records.STREAMFLOW_MEAS2_T; import usace.cwms.db.jooq.codegen.udt.records.STREAMFLOW_MEAS2_TAB_T; public final class MeasurementDao extends JooqDao { static final XmlMapper XML_MAPPER = buildXmlMapper(); - public static final String IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY = "measurement.ignoreExistingCheckForBulkUpdate"; public MeasurementDao(DSLContext dsl) { super(dsl); @@ -151,63 +148,6 @@ private void storeMeasurementsJooq(Connection conn, List measuremen } } - /** - * Updates an existing measurement - * @param measurement - the measurement to update - */ - public void updateMeasurement(Measurement measurement) { - connection(dsl, conn -> { - setOffice(conn, measurement.getOfficeId()); - verifyMeasurementExists(conn, measurement); - storeMeasurementJooq(conn, measurement, false); - }); - } - - /** - * Updates a list of existing measurements - * @param measurements - the measurements to update - */ - public void updateMeasurements(List measurements) - { - connection(dsl, conn -> { - if(!measurements.isEmpty()) { - List> measurementsByOffice = new ArrayList<>(measurements.stream() - .collect(Collectors.groupingBy(Measurement::getOfficeId)) - .values()); - for (List measurementsList : measurementsByOffice) { - Measurement measurement = measurements.get(0); - String officeId = measurement.getOfficeId(); - setOffice(conn, officeId); - //group measurementsList by locationId - List> measurementsByLocation = new ArrayList<>(measurementsList.stream() - .collect(Collectors.groupingBy(Measurement::getLocationId)) - .values()); - for (List locationMeasurements : measurementsByLocation) { - String locationId = locationMeasurements.get(0).getLocationId(); - verifyMeasurementsExists(conn, officeId, locationId, locationMeasurements); - storeMeasurementsJooq(conn, locationMeasurements, false); - } - } - } - }); - } - - // Helper method to retrieve existing measurement numbers from the database - private List getExistingMeasurementNumbers(Connection conn, String officeId, String locationId, List measurementNumbers) { - usace.cwms.db.jooq.codegen.tables.AV_STREAMFLOW_MEAS view = AV_STREAMFLOW_MEAS; - return getDslContext(conn, officeId) - .selectDistinct(view.LOCATION_ID, view.OFFICE_ID, view.MEAS_NUMBER) - .from(view) - .where(view.LOCATION_ID.eq(locationId) - .and(view.OFFICE_ID.eq(officeId)) - .and(view.MEAS_NUMBER.in(measurementNumbers))) - .groupBy(view.LOCATION_ID, view.OFFICE_ID, view.MEAS_NUMBER) - .fetch() - .stream() - .map(r -> r.get(view.MEAS_NUMBER)) - .collect(Collectors.toList()); - } - /** * Delete a measurement * @@ -229,14 +169,6 @@ public void deleteMeasurements(String officeId, String locationId, Instant minDa }); } - private void verifyMeasurementExists(Connection conn, Measurement measurement) { - List measurements = retrieveMeasurementsJooq(conn, measurement.getOfficeId(), measurement.getLocationId(), UnitSystem.EN.toString(), - null, null, null, null, measurement.getNumber(), measurement.getNumber(), null, null, null, null, OracleTypeMap.GMT_TIME_ZONE); - if (measurements.isEmpty() || measurements.stream().noneMatch(lt -> lt.getNumber().equals(measurement.getNumber()))) { - throw new NotFoundException("Could not find measurement."); - } - } - private void verifyMeasurementsExists(Connection conn, String officeId, String locationId, String minNum, String maxNum) { List measurements = retrieveMeasurementsJooq(conn, officeId, locationId, UnitSystem.EN.toString(), null, null, null, null, minNum, maxNum, null, null, null, null, OracleTypeMap.GMT_TIME_ZONE); @@ -245,27 +177,6 @@ private void verifyMeasurementsExists(Connection conn, String officeId, String l } } - private void verifyMeasurementsExists(Connection conn, String officeId, String locationId, List locationMeasurements) { - List measurementNumbers = locationMeasurements.stream() - .map(Measurement::getNumber) - .collect(Collectors.toList()); - - // Retrieve existing measurements from the database - List missingNumbers = new ArrayList<>(); - if(!Boolean.getBoolean(IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY)) - { - List existingNumbers = getExistingMeasurementNumbers(conn, officeId, locationId, measurementNumbers); - missingNumbers = new ArrayList<>(measurementNumbers); - // Find missing numbers - missingNumbers.removeAll(existingNumbers); - } - - if (!missingNumbers.isEmpty()) { - throw new NotFoundException("Could not find measurements " + String.join(",", missingNumbers) + - " for " + locationId + " in office " + officeId + "."); - } - } - static String toDbXml(List measurements) throws JsonProcessingException { MeasurementsXmlDto xmlDto = convertMeasurementsToXmlDto(measurements); return XML_MAPPER.writeValueAsString(xmlDto); diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index ff18faae0..b3df125b7 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -54,7 +54,6 @@ import org.junit.jupiter.api.AfterAll; import static org.junit.jupiter.api.Assertions.assertNotNull; 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 org.hamcrest.Matchers.*; @@ -96,7 +95,6 @@ static void createAndStoreTestStream(String testLoc) throws SQLException { @AfterAll public static void tearDown() { - System.clearProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY); for (Stream stream : TEST_STREAMS) { try { CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); @@ -218,86 +216,6 @@ void test_create_retrieve_delete_measurement() throws IOException { .body("[0].usgs-measurement.air-temp", equalTo(measurement.getUsgsMeasurement().getAirTemp().floatValue())) .body("[0].usgs-measurement.water-temp", equalTo(measurement.getUsgsMeasurement().getWaterTemp().floatValue())); - - InputStream resourceUpdated = this.getClass().getResourceAsStream("/cwms/cda/api/measurement_updated.json"); - assertNotNull(resourceUpdated); - String jsonUpdated = IOUtils.toString(resourceUpdated, StandardCharsets.UTF_8); - assertNotNull(jsonUpdated); - Measurement updatedMeasurement = Formats.parseContent(new ContentType(Formats.JSON), jsonUpdated, Measurement.class); - - //Update the Measurement - given() - .log().ifValidationFails(LogDetail.ALL, true) - .accept(Formats.JSON) - .contentType(Formats.JSON) - .body(jsonUpdated) - .header(AUTH_HEADER, user.toHeaderValue()) - .when() - .redirects().follow(true) - .redirects().max(3) - .patch("/measurements/") - .then() - .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() - .statusCode(is(HttpServletResponse.SC_OK)); - - // Retrieve the Updated Measurement and assert that it exists with updated values - given() - .log().ifValidationFails(LogDetail.ALL, true) - .accept(Formats.JSON) - .queryParam(Controllers.OFFICE_MASK, updatedMeasurement.getId().getOfficeId()) - .queryParam(Controllers.ID_MASK, updatedMeasurement.getLocationId()) - .queryParam(Controllers.MIN_NUMBER, number) - .queryParam(Controllers.MAX_NUMBER, number) - .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) - .when() - .redirects().follow(true) - .redirects().max(3) - .get("/measurements/") - .then() - .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() - .statusCode(is(HttpServletResponse.SC_OK)) - .body("[0].height-unit", equalTo(updatedMeasurement.getHeightUnit())) - .body("[0].flow-unit", equalTo(updatedMeasurement.getFlowUnit())) - .body("[0].temp-unit", equalTo(updatedMeasurement.getTempUnit())) - .body("[0].velocity-unit", equalTo(updatedMeasurement.getVelocityUnit())) - .body("[0].area-unit", equalTo(updatedMeasurement.getAreaUnit())) - .body("[0].used", equalTo(updatedMeasurement.isUsed())) - .body("[0].agency", equalTo(updatedMeasurement.getAgency())) - .body("[0].party", equalTo(updatedMeasurement.getParty())) - .body("[0].wm-comments", equalTo(updatedMeasurement.getWmComments())) - .body("[0].instant", equalTo(updatedMeasurement.getInstant().toString())) - .body("[0].number", equalTo(updatedMeasurement.getNumber())) - .body("[0].id.name", equalTo(updatedMeasurement.getLocationId())) - .body("[0].id.office-id", equalTo(updatedMeasurement.getOfficeId())) - .body("[0].streamflow-measurement.gage-height", equalTo(updatedMeasurement.getStreamflowMeasurement().getGageHeight().floatValue())) - .body("[0].streamflow-measurement.flow", equalTo(updatedMeasurement.getStreamflowMeasurement().getFlow().floatValue())) - .body("[0].streamflow-measurement.quality", equalTo(updatedMeasurement.getStreamflowMeasurement().getQuality())) - .body("[0].supplemental-streamflow-measurement.channel-flow", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) - .body("[0].supplemental-streamflow-measurement.overbank-flow", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) - .body("[0].supplemental-streamflow-measurement.overbank-max-depth", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) - .body("[0].supplemental-streamflow-measurement.channel-max-depth", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) - .body("[0].supplemental-streamflow-measurement.avg-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) - .body("[0].supplemental-streamflow-measurement.surface-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) - .body("[0].supplemental-streamflow-measurement.max-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) - .body("[0].supplemental-streamflow-measurement.effective-flow-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) - .body("[0].supplemental-streamflow-measurement.cross-sectional-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) - .body("[0].supplemental-streamflow-measurement.mean-gage", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) - .body("[0].supplemental-streamflow-measurement.top-width", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) - .body("[0].supplemental-streamflow-measurement.main-channel-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) - .body("[0].supplemental-streamflow-measurement.overbank-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) - .body("[0].usgs-measurement.remarks", equalTo(updatedMeasurement.getUsgsMeasurement().getRemarks())) - .body("[0].usgs-measurement.current-rating", equalTo(updatedMeasurement.getUsgsMeasurement().getCurrentRating())) - .body("[0].usgs-measurement.control-condition", equalTo(updatedMeasurement.getUsgsMeasurement().getControlCondition())) - .body("[0].usgs-measurement.flow-adjustment", equalTo(updatedMeasurement.getUsgsMeasurement().getFlowAdjustment())) - .body("[0].usgs-measurement.shift-used", equalTo(updatedMeasurement.getUsgsMeasurement().getShiftUsed().floatValue())) - .body("[0].usgs-measurement.percent-difference", equalTo(updatedMeasurement.getUsgsMeasurement().getPercentDifference().floatValue())) - .body("[0].usgs-measurement.delta-height", equalTo(updatedMeasurement.getUsgsMeasurement().getDeltaHeight().floatValue())) - .body("[0].usgs-measurement.delta-time", equalTo(updatedMeasurement.getUsgsMeasurement().getDeltaTime().floatValue())) - .body("[0].usgs-measurement.air-temp", equalTo(updatedMeasurement.getUsgsMeasurement().getAirTemp().floatValue())) - .body("[0].usgs-measurement.water-temp", equalTo(updatedMeasurement.getUsgsMeasurement().getWaterTemp().floatValue())); - // Delete the Measurement given() .log().ifValidationFails(LogDetail.ALL, true) @@ -337,7 +255,6 @@ void test_create_retrieve_delete_measurement() throws IOException { @Test @MinimumSchema(MINIMUM_SCHEMA) void test_create_retrieve_delete_measurement_multiple() throws IOException { - System.setProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY, String.valueOf(true)); InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurements.json"); assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); @@ -458,127 +375,6 @@ void test_create_retrieve_delete_measurement_multiple() throws IOException { .body("[1].usgs-measurement.air-temp", equalTo(measurement2.getUsgsMeasurement().getAirTemp().floatValue())) .body("[1].usgs-measurement.water-temp", equalTo(measurement2.getUsgsMeasurement().getWaterTemp().floatValue())); - - InputStream resourceUpdated = this.getClass().getResourceAsStream("/cwms/cda/api/measurements_updated.json"); - assertNotNull(resourceUpdated); - String jsonUpdated = IOUtils.toString(resourceUpdated, StandardCharsets.UTF_8); - assertNotNull(jsonUpdated); - List measurementsUpdated = Formats.parseContentList(new ContentType(Formats.JSON), jsonUpdated, Measurement.class); - Measurement updatedMeasurement = measurementsUpdated.get(0); - Measurement updatedMeasurement2 = measurementsUpdated.get(1); - - - //Update the Measurement - given() - .log().ifValidationFails(LogDetail.ALL, true) - .accept(Formats.JSON) - .contentType(Formats.JSON) - .body(jsonUpdated) - .header(AUTH_HEADER, user.toHeaderValue()) - .when() - .redirects().follow(true) - .redirects().max(3) - .patch("/measurements/") - .then() - .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() - .statusCode(is(HttpServletResponse.SC_OK)); - - // Retrieve the Updated Measurements and assert that they exists with updated values - String locationId = updatedMeasurement.getLocationId(); - given() - .log().ifValidationFails(LogDetail.ALL, true) - .accept(Formats.JSON) - .queryParam(Controllers.OFFICE_MASK, OFFICE_ID) - .queryParam(Controllers.ID_MASK, locationId) - .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) - .when() - .redirects().follow(true) - .redirects().max(3) - .get("/measurements/") - .then() - .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() - .statusCode(is(HttpServletResponse.SC_OK)) - .body("[0].height-unit", equalTo(updatedMeasurement.getHeightUnit())) - .body("[0].flow-unit", equalTo(updatedMeasurement.getFlowUnit())) - .body("[0].temp-unit", equalTo(updatedMeasurement.getTempUnit())) - .body("[0].velocity-unit", equalTo(updatedMeasurement.getVelocityUnit())) - .body("[0].area-unit", equalTo(updatedMeasurement.getAreaUnit())) - .body("[0].used", equalTo(updatedMeasurement.isUsed())) - .body("[0].agency", equalTo(updatedMeasurement.getAgency())) - .body("[0].party", equalTo(updatedMeasurement.getParty())) - .body("[0].wm-comments", equalTo(updatedMeasurement.getWmComments())) - .body("[0].instant", equalTo(updatedMeasurement.getInstant().toString())) - .body("[0].number", equalTo(updatedMeasurement.getNumber())) - .body("[0].id.name", equalTo(updatedMeasurement.getLocationId())) - .body("[0].id.office-id", equalTo(updatedMeasurement.getOfficeId())) - .body("[0].streamflow-measurement.gage-height", equalTo(updatedMeasurement.getStreamflowMeasurement().getGageHeight().floatValue())) - .body("[0].streamflow-measurement.flow", equalTo(updatedMeasurement.getStreamflowMeasurement().getFlow().floatValue())) - .body("[0].streamflow-measurement.quality", equalTo(updatedMeasurement.getStreamflowMeasurement().getQuality())) - .body("[0].supplemental-streamflow-measurement.channel-flow", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) - .body("[0].supplemental-streamflow-measurement.overbank-flow", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) - .body("[0].supplemental-streamflow-measurement.overbank-max-depth", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) - .body("[0].supplemental-streamflow-measurement.channel-max-depth", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) - .body("[0].supplemental-streamflow-measurement.avg-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) - .body("[0].supplemental-streamflow-measurement.surface-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) - .body("[0].supplemental-streamflow-measurement.max-velocity", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) - .body("[0].supplemental-streamflow-measurement.effective-flow-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) - .body("[0].supplemental-streamflow-measurement.cross-sectional-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) - .body("[0].supplemental-streamflow-measurement.mean-gage", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) - .body("[0].supplemental-streamflow-measurement.top-width", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) - .body("[0].supplemental-streamflow-measurement.main-channel-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) - .body("[0].supplemental-streamflow-measurement.overbank-area", equalTo(updatedMeasurement.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) - .body("[0].usgs-measurement.remarks", equalTo(updatedMeasurement.getUsgsMeasurement().getRemarks())) - .body("[0].usgs-measurement.current-rating", equalTo(updatedMeasurement.getUsgsMeasurement().getCurrentRating())) - .body("[0].usgs-measurement.control-condition", equalTo(updatedMeasurement.getUsgsMeasurement().getControlCondition())) - .body("[0].usgs-measurement.flow-adjustment", equalTo(updatedMeasurement.getUsgsMeasurement().getFlowAdjustment())) - .body("[0].usgs-measurement.shift-used", equalTo(updatedMeasurement.getUsgsMeasurement().getShiftUsed().floatValue())) - .body("[0].usgs-measurement.percent-difference", equalTo(updatedMeasurement.getUsgsMeasurement().getPercentDifference().floatValue())) - .body("[0].usgs-measurement.delta-height", equalTo(updatedMeasurement.getUsgsMeasurement().getDeltaHeight().floatValue())) - .body("[0].usgs-measurement.delta-time", equalTo(updatedMeasurement.getUsgsMeasurement().getDeltaTime().floatValue())) - .body("[0].usgs-measurement.air-temp", equalTo(updatedMeasurement.getUsgsMeasurement().getAirTemp().floatValue())) - .body("[0].usgs-measurement.water-temp", equalTo(updatedMeasurement.getUsgsMeasurement().getWaterTemp().floatValue())) - .body("[1].height-unit", equalTo(updatedMeasurement2.getHeightUnit())) - .body("[1].flow-unit", equalTo(updatedMeasurement2.getFlowUnit())) - .body("[1].temp-unit", equalTo(updatedMeasurement2.getTempUnit())) - .body("[1].velocity-unit", equalTo(updatedMeasurement2.getVelocityUnit())) - .body("[1].area-unit", equalTo(updatedMeasurement2.getAreaUnit())) - .body("[1].used", equalTo(updatedMeasurement2.isUsed())) - .body("[1].agency", equalTo(updatedMeasurement2.getAgency())) - .body("[1].party", equalTo(updatedMeasurement2.getParty())) - .body("[1].wm-comments", equalTo(updatedMeasurement2.getWmComments())) - .body("[1].instant", equalTo(updatedMeasurement2.getInstant().toString())) - .body("[1].number", equalTo(updatedMeasurement2.getNumber())) - .body("[1].id.name", equalTo(updatedMeasurement2.getLocationId())) - .body("[1].id.office-id", equalTo(updatedMeasurement2.getOfficeId())) - .body("[1].streamflow-measurement.gage-height", equalTo(updatedMeasurement2.getStreamflowMeasurement().getGageHeight().floatValue())) - .body("[1].streamflow-measurement.flow", equalTo(updatedMeasurement2.getStreamflowMeasurement().getFlow().floatValue())) - .body("[1].streamflow-measurement.quality", equalTo(updatedMeasurement2.getStreamflowMeasurement().getQuality())) - .body("[1].supplemental-streamflow-measurement.channel-flow", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getChannelFlow().floatValue())) - .body("[1].supplemental-streamflow-measurement.overbank-flow", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getOverbankFlow().floatValue())) - .body("[1].supplemental-streamflow-measurement.overbank-max-depth", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getOverbankMaxDepth().floatValue())) - .body("[1].supplemental-streamflow-measurement.channel-max-depth", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getChannelMaxDepth().floatValue())) - .body("[1].supplemental-streamflow-measurement.avg-velocity", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getAvgVelocity().floatValue())) - .body("[1].supplemental-streamflow-measurement.surface-velocity", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getSurfaceVelocity().floatValue())) - .body("[1].supplemental-streamflow-measurement.max-velocity", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getMaxVelocity().floatValue())) - .body("[1].supplemental-streamflow-measurement.effective-flow-area", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getEffectiveFlowArea().floatValue())) - .body("[1].supplemental-streamflow-measurement.cross-sectional-area", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getCrossSectionalArea().floatValue())) - .body("[1].supplemental-streamflow-measurement.mean-gage", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getMeanGage().floatValue())) - .body("[1].supplemental-streamflow-measurement.top-width", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getTopWidth().floatValue())) - .body("[1].supplemental-streamflow-measurement.main-channel-area", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getMainChannelArea().floatValue())) - .body("[1].supplemental-streamflow-measurement.overbank-area", equalTo(updatedMeasurement2.getSupplementalStreamflowMeasurement().getOverbankArea().floatValue())) - .body("[1].usgs-measurement.remarks", equalTo(updatedMeasurement2.getUsgsMeasurement().getRemarks())) - .body("[1].usgs-measurement.current-rating", equalTo(updatedMeasurement2.getUsgsMeasurement().getCurrentRating())) - .body("[1].usgs-measurement.control-condition", equalTo(updatedMeasurement2.getUsgsMeasurement().getControlCondition())) - .body("[1].usgs-measurement.flow-adjustment", equalTo(updatedMeasurement2.getUsgsMeasurement().getFlowAdjustment())) - .body("[1].usgs-measurement.shift-used", equalTo(updatedMeasurement2.getUsgsMeasurement().getShiftUsed().floatValue())) - .body("[1].usgs-measurement.percent-difference", equalTo(updatedMeasurement2.getUsgsMeasurement().getPercentDifference().floatValue())) - .body("[1].usgs-measurement.delta-height", equalTo(updatedMeasurement2.getUsgsMeasurement().getDeltaHeight().floatValue())) - .body("[1].usgs-measurement.delta-time", equalTo(updatedMeasurement2.getUsgsMeasurement().getDeltaTime().floatValue())) - .body("[1].usgs-measurement.air-temp", equalTo(updatedMeasurement2.getUsgsMeasurement().getAirTemp().floatValue())) - .body("[1].usgs-measurement.water-temp", equalTo(updatedMeasurement2.getUsgsMeasurement().getWaterTemp().floatValue())); - // Delete the Measurements given() .log().ifValidationFails(LogDetail.ALL, true) @@ -588,7 +384,7 @@ void test_create_retrieve_delete_measurement_multiple() throws IOException { .when() .redirects().follow(true) .redirects().max(3) - .delete("/measurements/" + locationId) + .delete("/measurements/" + measurement1.getLocationId()) .then() .log().ifValidationFails(LogDetail.ALL, true) .assertThat() @@ -599,7 +395,7 @@ void test_create_retrieve_delete_measurement_multiple() throws IOException { .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSON) .queryParam(Controllers.OFFICE, OFFICE_ID) - .queryParam(Controllers.ID_MASK, locationId) + .queryParam(Controllers.ID_MASK, measurement1.getLocationId()) .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) .when() .redirects().follow(true) @@ -611,35 +407,6 @@ void test_create_retrieve_delete_measurement_multiple() throws IOException { .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); } - @Test - @MinimumSchema(MINIMUM_SCHEMA) - void test_update_does_not_exist() throws Exception { - System.clearProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY); - TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; - - InputStream resourceUpdated = this.getClass().getResourceAsStream("/cwms/cda/api/measurements_updated.json"); - assertNotNull(resourceUpdated); - String jsonUpdated = IOUtils.toString(resourceUpdated, StandardCharsets.UTF_8); - assertNotNull(jsonUpdated); - List measurementsUpdated = Formats.parseContentList(new ContentType(Formats.JSON), jsonUpdated, Measurement.class); - - //Update the Measurement(s) that were never stored, and therefore doesn't exist in the db - given() - .log().ifValidationFails(LogDetail.ALL, true) - .accept(Formats.JSON) - .contentType(Formats.JSON) - .body(jsonUpdated) - .header(AUTH_HEADER, user.toHeaderValue()) - .when() - .redirects().follow(true) - .redirects().max(3) - .patch("/measurements/") - .then() - .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() - .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); - } - @Test @MinimumSchema(MINIMUM_SCHEMA) void test_delete_does_not_exist() { diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java index ce1b66912..6779656fd 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java @@ -28,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -132,17 +131,6 @@ void testRoundTrip() throws Exception { assertNotNull(meas2Found); DTOMatch.assertMatch(meas2, meas2Found); - //test update - meas1 = buildMeasurement1(streamLocId, 200); - measurementDao.updateMeasurement(meas1); - - List retrievedMeasurements = measurementDao.retrieveMeasurements(OFFICE_ID, streamLocId, null, null, UnitSystem.EN.getValue(), - null, null, null, null, meas1.getNumber(), meas1.getNumber(), null, null); - DTOMatch.assertMatch(meas1, retrievedMeasurements.get(0)); - - Measurement doesntExist = buildMeasurementDoesntExist(streamLocId); - assertThrows(NotFoundException.class, () -> measurementDao.updateMeasurement(doesntExist)); - //delete measurements measurementDao.deleteMeasurements(meas1.getId().getOfficeId(), meas1.getId().getName(), null, null, null, null, null, null, null, null, null, null, null); measurementDao.deleteMeasurements(meas2.getId().getOfficeId(), meas2.getId().getName(), null, null, null, null, null, null, null, null, null, null, null); @@ -184,7 +172,6 @@ void testRoundTripMultipleStore() throws Exception { StreamLocation streamLocation2 = StreamLocationDaoTestIT.buildTestStreamLocation("TEST_STREAM_123", streamLocId2, OFFICE_ID,11.0, Bank.RIGHT); try { - System.setProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY, String.valueOf(true)); //store stream locations streamLocationDao.storeStreamLocation(streamLocation, false); streamLocationDao.storeStreamLocation(streamLocation2, false); @@ -224,16 +211,6 @@ void testRoundTripMultipleStore() throws Exception { assertNotNull(meas2Found); DTOMatch.assertMatch(meas2, meas2Found); - //test update - meas1 = buildMeasurement1(streamLocId, 400); - meas1B = buildMeasurement2(streamLocId, 500); - List updatedMeasurements = new ArrayList<>(); - updatedMeasurements.add(meas1); - updatedMeasurements.add(meas1B); - measurementDao.updateMeasurements(updatedMeasurements); - - System.clearProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY); - retrievedMeasurements = measurementDao.retrieveMeasurements(OFFICE_ID, streamLocId, null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null); @@ -252,10 +229,6 @@ void testRoundTripMultipleStore() throws Exception { assertNotNull(retrievedMeas1B); DTOMatch.assertMatch(meas1B, retrievedMeas1B); - Measurement doesntExist = buildMeasurementDoesntExist(streamLocId); - updatedMeasurements.add(doesntExist); - assertThrows(NotFoundException.class, () -> measurementDao.updateMeasurements(updatedMeasurements)); - //delete measurements measurementDao.deleteMeasurements(meas1.getId().getOfficeId(), meas1.getId().getName(), null, null, null, null, null, null, null, null, null, null, null); measurementDao.deleteMeasurements(meas2.getId().getOfficeId(), meas2.getId().getName(), null, null, null, null, null, null, null, null, null, null, null); @@ -267,7 +240,6 @@ void testRoundTripMultipleStore() throws Exception { assertThrows(NotFoundException.class, () -> measurementDao.retrieveMeasurements(meas2F.getId().getOfficeId(), meas2F.getId().getName(), null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null)); } finally { - System.clearProperty(MeasurementDao.IGNORE_EXISTING_CHECK_FOR_BULK_UPDATE_PROPERTY); //delete stream locations streamLocationDao.deleteStreamLocation( streamLocation.getStreamLocationNode().getId().getOfficeId(), diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/measurement_updated.json b/cwms-data-api/src/test/resources/cwms/cda/api/measurement_updated.json deleted file mode 100644 index e137289df..000000000 --- a/cwms-data-api/src/test/resources/cwms/cda/api/measurement_updated.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "height-unit": "ft", - "flow-unit": "cfs", - "temp-unit": "F", - "velocity-unit": "fps", - "area-unit": "ft2", - "used": true, - "agency": "USGS", - "party": "SPK", - "wm-comments": "Measurement made during normal flow conditions.", - "instant": "2024-09-16T00:00:00Z", - "number": "123456", - "id": { - "name": "StreamLoc321", - "office-id": "SPK" - }, - "streamflow-measurement": { - "gage-height": 5.8, - "flow": 350.0, - "quality": "G" - }, - "supplemental-streamflow-measurement": { - "channel-flow": 300.0, - "overbank-flow": 50.0, - "overbank-max-depth": 5.0, - "channel-max-depth": 10.0, - "avg-velocity": 1.5, - "surface-velocity": 2.0, - "max-velocity": 3.0, - "effective-flow-area": 200.0, - "cross-sectional-area": 250.0, - "mean-gage": 20.0, - "top-width": 30.0, - "main-channel-area": 150.0, - "overbank-area": 80.0 - }, - "usgs-measurement": { - "remarks": "Remarks", - "current-rating": "1", - "control-condition": "FILL", - "flow-adjustment": "OTHR", - "shift-used": 0.1, - "percent-difference": 5.0, - "delta-height": 0.05, - "delta-time": 10.0, - "air-temp": 20.0, - "water-temp": 15.0 - } -} diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/measurements_updated.json b/cwms-data-api/src/test/resources/cwms/cda/api/measurements_updated.json deleted file mode 100644 index e681d6e73..000000000 --- a/cwms-data-api/src/test/resources/cwms/cda/api/measurements_updated.json +++ /dev/null @@ -1,100 +0,0 @@ -[ - { - "height-unit": "ft", - "flow-unit": "cfs", - "temp-unit": "F", - "velocity-unit": "fps", - "area-unit": "ft2", - "used": true, - "agency": "USGS", - "party": "SPK", - "wm-comments": "Measurement made during normal flow conditions.", - "instant": "2024-09-16T00:00:00Z", - "number": "123456", - "id": { - "name": "StreamLoc321", - "office-id": "SPK" - }, - "streamflow-measurement": { - "gage-height": 5.8, - "flow": 350.0, - "quality": "G" - }, - "supplemental-streamflow-measurement": { - "channel-flow": 300.0, - "overbank-flow": 50.0, - "overbank-max-depth": 5.0, - "channel-max-depth": 10.0, - "avg-velocity": 1.5, - "surface-velocity": 2.0, - "max-velocity": 3.0, - "effective-flow-area": 200.0, - "cross-sectional-area": 250.0, - "mean-gage": 20.0, - "top-width": 30.0, - "main-channel-area": 150.0, - "overbank-area": 80.0 - }, - "usgs-measurement": { - "remarks": "Remarks", - "current-rating": "1", - "control-condition": "FILL", - "flow-adjustment": "OTHR", - "shift-used": 0.1, - "percent-difference": 5.0, - "delta-height": 0.05, - "delta-time": 10.0, - "air-temp": 20.0, - "water-temp": 15.0 - } - }, - { - "height-unit": "ft", - "flow-unit": "cfs", - "temp-unit": "F", - "velocity-unit": "fps", - "area-unit": "ft2", - "used": true, - "agency": "USGS", - "party": "SPK", - "wm-comments": "Measurement made after recent rainfall2.", - "instant": "2024-09-17T12:00:00Z", - "number": "654321", - "id": { - "name": "StreamLoc321", - "office-id": "SPK" - }, - "streamflow-measurement": { - "gage-height": 6.4, - "flow": 279.0, - "quality": "F" - }, - "supplemental-streamflow-measurement": { - "channel-flow": 320.0, - "overbank-flow": 45.0, - "overbank-max-depth": 4.5, - "channel-max-depth": 9.8, - "avg-velocity": 1.8, - "surface-velocity": 2.5, - "max-velocity": 3.5, - "effective-flow-area": 220.0, - "cross-sectional-area": 240.0, - "mean-gage": 21.0, - "top-width": 32.0, - "main-channel-area": 163.0, - "overbank-area": 82.0 - }, - "usgs-measurement": { - "remarks": "Post-rain conditions.", - "current-rating": "2", - "control-condition": "FILL", - "flow-adjustment": "OTHR", - "shift-used": 0.15, - "percent-difference": 4.5, - "delta-height": 0.4, - "delta-time": 15.0, - "air-temp": 19.0, - "water-temp": 16.0 - } - } -] From 90176689924ae95950d5c4bee2049571661afb1e Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Thu, 17 Oct 2024 14:57:41 -0700 Subject: [PATCH 08/10] CWDB-225 - Updated to only support bulk store. single-store is captured via a single-element-array. Updated to support all optional query parameters for retrieval. --- .../main/java/cwms/cda/api/Controllers.java | 17 ++++ .../cwms/cda/api/MeasurementController.java | 85 ++++++++-------- .../cwms/cda/data/dao/MeasurementDao.java | 22 ----- .../cda/api/MeasurementControllerTestIT.java | 7 +- .../cwms/cda/data/dao/MeasurementDaoTest.java | 17 ---- .../cda/data/dao/MeasurementDaoTestIT.java | 80 +-------------- .../resources/cwms/cda/api/measurement.json | 98 ++++++++++--------- .../cwms/cda/data/dao/dbMeasurement.xml | 40 -------- 8 files changed, 116 insertions(+), 250 deletions(-) delete mode 100644 cwms-data-api/src/test/resources/cwms/cda/data/dao/dbMeasurement.xml diff --git a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java index f3a689ca2..52ce8a624 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java @@ -139,6 +139,13 @@ public final class Controllers { public static final String MAX_DATE = "max-date"; public static final String MIN_NUMBER = "min-number"; public static final String MAX_NUMBER = "max-number"; + public static final String MIN_HEIGHT = "min-height"; + public static final String MAX_HEIGHT = "max-height"; + public static final String MIN_FLOW = "min-flow"; + public static final String MAX_FLOW = "max-flow"; + public static final String AGENCY = "agency"; + public static final String QUALITY = "quality"; + public static final String GROUP_ID = "group-id"; public static final String REPLACE_ASSIGNED_LOCS = "replace-assigned-locs"; @@ -375,6 +382,16 @@ public static T requiredParamAs(io.javalin.http.Context ctx, String name, Cl .getOrThrow(e -> new RequiredQueryParameterException(name)); } + @Nullable + public static Double queryParamAsDouble(Context ctx, String param) { + Double retVal = null; + String numberStr = ctx.queryParam(param); + if (numberStr != null) { + retVal = Double.parseDouble(numberStr); + } + return retVal; + } + @Nullable public static ZonedDateTime queryParamAsZdt(Context ctx, String param, String timezone) { ZonedDateTime beginZdt = null; diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java index 72df06b33..cd0e6b12e 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java @@ -27,27 +27,36 @@ import com.codahale.metrics.MetricRegistry; import static com.codahale.metrics.MetricRegistry.name; import com.codahale.metrics.Timer; +import static cwms.cda.api.Controllers.AGENCY; 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.ID_MASK; +import static cwms.cda.api.Controllers.LOCATION_ID; import static cwms.cda.api.Controllers.MAX_DATE; +import static cwms.cda.api.Controllers.MAX_FLOW; +import static cwms.cda.api.Controllers.MAX_HEIGHT; import static cwms.cda.api.Controllers.MIN_DATE; +import static cwms.cda.api.Controllers.MIN_FLOW; +import static cwms.cda.api.Controllers.MIN_HEIGHT; import static cwms.cda.api.Controllers.NOT_SUPPORTED_YET; import static cwms.cda.api.Controllers.MIN_NUMBER; import static cwms.cda.api.Controllers.MAX_NUMBER; import static cwms.cda.api.Controllers.OFFICE; import static cwms.cda.api.Controllers.OFFICE_MASK; +import static cwms.cda.api.Controllers.QUALITY; +import static cwms.cda.api.Controllers.TIMEZONE; import static cwms.cda.api.Controllers.UNIT_SYSTEM; +import static cwms.cda.api.Controllers.queryParamAsDouble; +import static cwms.cda.api.Controllers.queryParamAsInstant; import static cwms.cda.api.Controllers.requiredParam; import cwms.cda.api.enums.UnitSystem; import cwms.cda.data.dao.MeasurementDao; import cwms.cda.data.dto.measurement.Measurement; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; -import cwms.cda.formatters.FormattingException; import io.javalin.apibuilder.CrudHandler; import io.javalin.core.util.Header; import io.javalin.http.Context; @@ -58,7 +67,6 @@ import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; import io.javalin.plugin.openapi.annotations.OpenApiResponse; import java.time.Instant; -import java.util.ArrayList; import org.jetbrains.annotations.NotNull; import org.jooq.DSLContext; @@ -90,8 +98,15 @@ private Timer.Context markAndTime(String subject) { @OpenApiParam(name = ID_MASK, description = "Location id mask for filtering measurements. Use null to retrieve measurements for all locations."), @OpenApiParam(name = MIN_NUMBER, description = "Minimum measurement number-id for filtering measurements."), @OpenApiParam(name = MAX_NUMBER, description = "Maximum measurement number-id for filtering measurements."), - @OpenApiParam(name = MIN_DATE, description = "Minimum date for filtering measurements in ISO-8601 format."), - @OpenApiParam(name = MAX_DATE, description = "Maximum date for filtering measurements in ISO-8601 format."), + @OpenApiParam(name = MIN_DATE, description = "Minimum date-time for filtering measurements in ISO-8601 format."), + @OpenApiParam(name = MAX_DATE, description = "Maximum date-time for filtering measurements in ISO-8601 format."), + @OpenApiParam(name = TIMEZONE, description = "Timezone for the date range."), + @OpenApiParam(name = MIN_HEIGHT, description = "Minimum height for filtering measurements."), + @OpenApiParam(name = MAX_HEIGHT, description = "Maximum height for filtering measurements."), + @OpenApiParam(name = MIN_FLOW, description = "Minimum flow for filtering measurements."), + @OpenApiParam(name = MAX_FLOW, description = "Maximum flow for filtering measurements."), + @OpenApiParam(name = AGENCY, description = "Agencies for filtering measurements."), + @OpenApiParam(name = QUALITY, description = "Quality for filtering measurements."), @OpenApiParam(name = UNIT_SYSTEM, description = "Specifies the unit system" + " of the response. Valid values for the unit field are: " + "\n* `EN` Specifies English unit system. Location values will be in the " @@ -113,21 +128,21 @@ public void getAll(@NotNull Context ctx) { String officeId = ctx.queryParam(OFFICE_MASK); String locationId = ctx.queryParam(ID_MASK); String unitSystem = ctx.queryParamAsClass(UNIT_SYSTEM, String.class).getOrDefault(UnitSystem.EN.value()); - Instant minDate = parseInstant(ctx.queryParam(MIN_DATE)); - Instant maxDate = parseInstant(ctx.queryParam(MAX_DATE)); + Instant minDate = queryParamAsInstant(ctx, MIN_DATE); + Instant maxDate = queryParamAsInstant(ctx, MAX_DATE); String minNum = ctx.queryParam(MIN_NUMBER); String maxNum = ctx.queryParam(MAX_NUMBER); - Number minHeight = null; - Number maxHeight = null; - Number minFlow = null; - Number maxFlow = null; - String agencies = null; - String qualities = null; + Number minHeight = queryParamAsDouble(ctx, MIN_HEIGHT); + Number maxHeight = queryParamAsDouble(ctx, MAX_HEIGHT); + Number minFlow = queryParamAsDouble(ctx, MIN_FLOW); + Number maxFlow = queryParamAsDouble(ctx, MAX_FLOW); + String agency = ctx.queryParam(AGENCY); + String quality = ctx.queryParam(QUALITY); try (Timer.Context ignored = markAndTime(GET_ALL)) { DSLContext dsl = getDslContext(ctx); MeasurementDao dao = new MeasurementDao(dsl); List measurements = dao.retrieveMeasurements(officeId, locationId, minDate, maxDate, unitSystem, - minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agencies, qualities); + minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agency, quality); String formatHeader = ctx.header(Header.ACCEPT); ContentType contentType = Formats.parseHeader(formatHeader, Measurement.class); ctx.contentType(contentType.toString()); @@ -151,7 +166,7 @@ public void getOne(@NotNull Context ctx, @NotNull String locationId) { requestBody = @OpenApiRequestBody( content = { @OpenApiContent(isArray = true, from = Measurement.class, type = Formats.JSONV1), - @OpenApiContent(from = Measurement.class, type = Formats.JSON) + @OpenApiContent(isArray = true, from = Measurement.class, type = Formats.JSON) }, required = true), queryParams = { @@ -171,17 +186,17 @@ public void create(Context ctx) { try (Timer.Context ignored = markAndTime(CREATE)) { String formatHeader = ctx.req.getContentType(); ContentType contentType = Formats.parseHeader(formatHeader, Measurement.class); - List measurements = parseMeasurements(ctx, contentType); + List measurements = Formats.parseContentList(contentType, ctx.body(), Measurement.class); boolean failIfExists = ctx.queryParamAsClass(FAIL_IF_EXISTS, Boolean.class).getOrDefault(true); DSLContext dsl = getDslContext(ctx); MeasurementDao dao = new MeasurementDao(dsl); - if(measurements.size() == 1) { - dao.storeMeasurement(measurements.get(0), failIfExists); - ctx.status(HttpServletResponse.SC_CREATED).json("Created Measurement"); - } else { - dao.storeMeasurements(measurements, failIfExists); - ctx.status(HttpServletResponse.SC_CREATED).json("Created Measurements"); + dao.storeMeasurements(measurements, failIfExists); + String statusMsg = "Created Measurement"; + if(measurements.size() > 1) + { + statusMsg += "s"; } + ctx.status(HttpServletResponse.SC_CREATED).json(statusMsg); } } @@ -194,12 +209,17 @@ public void update(@NotNull Context ctx, @NotNull String locationId) { } @OpenApi( + pathParams = { + @OpenApiParam(name = LOCATION_ID, description = "Specifies the location-id of " + + "the measurement(s) to be deleted."), + }, queryParams = { @OpenApiParam(name = OFFICE, required = true, description = "Specifies the office of the measurements to delete"), @OpenApiParam(name = MIN_NUMBER, description = "Specifies the min number-id of the measurement to delete."), @OpenApiParam(name = MAX_NUMBER, description = "Specifies the max number-id of the measurement to delete."), @OpenApiParam(name = MIN_DATE, description = "Specifies the minimum date (in ISO-8601 format) of the measurement to delete."), @OpenApiParam(name = MAX_DATE, description = "Specifies the maximum date (in ISO-8601 format) of the measurement to delete."), + @OpenApiParam(name = TIMEZONE, description = "Specifies the timezone of the date range.") }, description = "Delete an existing measurement.", method = HttpMethod.DELETE, @@ -214,8 +234,8 @@ public void delete(@NotNull Context ctx, @NotNull String locationId) { String officeId = requiredParam(ctx, OFFICE); String minNum = ctx.queryParam(MIN_NUMBER); String maxNum = ctx.queryParam(MAX_NUMBER); - Instant minDate = parseInstant(ctx.queryParam(MIN_DATE)); - Instant maxDate = parseInstant(ctx.queryParam(MAX_DATE)); + Instant minDate = queryParamAsInstant(ctx, MIN_DATE); + Instant maxDate = queryParamAsInstant(ctx, MAX_DATE); try (Timer.Context ignored = markAndTime(DELETE)) { DSLContext dsl = getDslContext(ctx); MeasurementDao dao = new MeasurementDao(dsl); @@ -225,23 +245,4 @@ public void delete(@NotNull Context ctx, @NotNull String locationId) { } } - private Instant parseInstant(String date) { - Instant retVal = null; - if(date != null && !date.isEmpty()) { - retVal = Instant.parse(date); - } - return retVal; - } - - static List parseMeasurements(@NotNull Context ctx, ContentType contentType) { - List measurements; - try { - measurements = Formats.parseContentList(contentType, ctx.body(), Measurement.class); - } catch (FormattingException e) { - Measurement measurement = Formats.parseContent(contentType, ctx.body(), Measurement.class); - measurements = new ArrayList<>(); - measurements.add(measurement); - } - return measurements; - } } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java index 6be46ee21..36ecd05bb 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java @@ -112,23 +112,6 @@ private static List retrieveMeasurementsJooq(Connection conn, Strin return retVal; } - /** - * Store a measurement - * - * @param measurement - the measurement to store - * @param failIfExists - if true, fail if the measurement already exists - */ - public void storeMeasurement(Measurement measurement, boolean failIfExists) { - connection(dsl, conn -> storeMeasurementJooq(conn, measurement, failIfExists)); - } - - private void storeMeasurementJooq(Connection conn, Measurement measurement, boolean failIfExists) throws SQLException, JsonProcessingException { - setOffice(conn, measurement.getOfficeId()); - String failIfExistsStr = formatBool(failIfExists); - String xml = toDbXml(measurement); - CWMS_STREAM_PACKAGE.call_STORE_MEAS_XML(DSL.using(conn).configuration(), xml, failIfExistsStr); - } - /** * Store a list of measurements * @param measurements - the measurements to store @@ -182,11 +165,6 @@ static String toDbXml(List measurements) throws JsonProcessingExcep return XML_MAPPER.writeValueAsString(xmlDto); } - static String toDbXml(Measurement measurement) throws JsonProcessingException { - MeasurementXmlDto xmlDto = convertMeasurementToXmlDto(measurement); - return XML_MAPPER.writeValueAsString(xmlDto); - } - static Measurement fromJooqMeasurementRecord(STREAMFLOW_MEAS2_T record) { LocationTemplate locationTemplate = new LocationTemplate(record.getLOCATION().getOFFICE_ID(), record.getLOCATION().getBASE_LOCATION_ID(), record.getLOCATION().getSUB_LOCATION_ID()); diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index b3df125b7..5451340bf 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -47,6 +47,7 @@ import java.sql.SQLException; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.servlet.http.HttpServletResponse; import mil.army.usace.hec.test.database.CwmsDatabaseContainer; @@ -136,8 +137,8 @@ void test_create_retrieve_delete_measurement() throws IOException { assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); assertNotNull(json); - Measurement measurement = Formats.parseContent(new ContentType(Formats.JSON), json, Measurement.class); - + List measurements = Formats.parseContentList(new ContentType(Formats.JSON), json, Measurement.class); + Measurement measurement = measurements.get(0); TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; // Create the Measurement @@ -167,6 +168,8 @@ void test_create_retrieve_delete_measurement() throws IOException { .queryParam(Controllers.ID_MASK, measurement.getLocationId()) .queryParam(Controllers.MIN_NUMBER, number) .queryParam(Controllers.MAX_NUMBER, number) + .queryParam(Controllers.MIN_HEIGHT, 0.0) + .queryParam(Controllers.MAX_FLOW, 1000) .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) .when() .redirects().follow(true) diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTest.java index 95ead4790..0007a3b0b 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTest.java @@ -115,23 +115,6 @@ void testConvertToXmlMeasurementDto() assertMatch(meas, xmlDto); } - @Test - void testToDbXml() throws Exception - { - InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dao/dbMeasurement.xml"); - assertNotNull(resource); - String expectedXml = IOUtils.toString(resource, StandardCharsets.UTF_8); - MeasurementDao.MeasurementXmlDto expectedXmlDto = MeasurementDao.XML_MAPPER.readValue(expectedXml, MeasurementDao.MeasurementXmlDto.class); - - Measurement meas = buildTestMeasurement(); - String xml = MeasurementDao.toDbXml(meas); - assertNotNull(xml); - assertFalse(xml.isEmpty()); - MeasurementDao.MeasurementXmlDto actualXmlDto = MeasurementDao.XML_MAPPER.readValue(xml, MeasurementDao.MeasurementXmlDto.class); - - assertMatch(expectedXmlDto, actualXmlDto); - } - @Test void testConvertToMeasurementsXmlDto() { Measurement meas1 = buildTestMeasurement(); diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java index 6779656fd..50611c0b9 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java @@ -81,85 +81,7 @@ public static void tearDown() { @Test @MinimumSchema(MINIMUM_SCHEMA) - void testRoundTrip() throws Exception { - CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); - String webUser = CwmsDataApiSetupCallback.getWebUser(); - databaseLink.connection(c -> { - DSLContext context = getDslContext(c, databaseLink.getOfficeId()); - StreamLocationDao streamLocationDao = new StreamLocationDao(context); - //build stream locations - String streamLocId = STREAM_LOC_IDS.get(0); - StreamLocation streamLocation = StreamLocationDaoTestIT.buildTestStreamLocation("TEST_STREAM_123", streamLocId, OFFICE_ID, 10.0, Bank.LEFT); - String streamLocId2 = STREAM_LOC_IDS.get(1); - StreamLocation streamLocation2 = StreamLocationDaoTestIT.buildTestStreamLocation("TEST_STREAM_123", streamLocId2, OFFICE_ID, 11.0, Bank.RIGHT); - - try { - //store stream locations - streamLocationDao.storeStreamLocation(streamLocation, false); - streamLocationDao.storeStreamLocation(streamLocation2, false); - - Measurement meas1 = buildMeasurement1(streamLocId); - Measurement meas1B = buildMeasurement2(streamLocId); - - Measurement meas2 = buildMeasurement1(streamLocId2); - - MeasurementDao measurementDao = new MeasurementDao(context); - measurementDao.storeMeasurement(meas1, false); - measurementDao.storeMeasurement(meas1B, false); - measurementDao.storeMeasurement(meas2, false); - - List measurements = measurementDao.retrieveMeasurements(OFFICE_ID, streamLocId, null, null, UnitSystem.EN.getValue(), - null, null, null, null, null, null, null, null); - assertEquals(2, measurements.size()); - - DTOMatch.assertMatch(meas1, measurements.get(0)); - DTOMatch.assertMatch(meas1B, measurements.get(1)); - - List measurementsAll = measurementDao.retrieveMeasurements(OFFICE_ID, null, null, null, UnitSystem.EN.getValue(), - null, null, null, null, null, null, null, null); - List meas1List = measurementsAll.stream() - .filter(m -> m.getLocationId().equals(streamLocId)) - .collect(Collectors.toList()); - assertEquals(2, meas1List.size()); - DTOMatch.assertMatch(meas1, meas1List.get(0)); - DTOMatch.assertMatch(meas1B, meas1List.get(1)); - - Measurement meas2Found = measurementsAll.stream() - .filter(m -> m.getLocationId().equals(streamLocId2)) - .findFirst() - .orElse(null); - assertNotNull(meas2Found); - DTOMatch.assertMatch(meas2, meas2Found); - - //delete measurements - measurementDao.deleteMeasurements(meas1.getId().getOfficeId(), meas1.getId().getName(), null, null, null, null, null, null, null, null, null, null, null); - measurementDao.deleteMeasurements(meas2.getId().getOfficeId(), meas2.getId().getName(), null, null, null, null, null, null, null, null, null, null, null); - - final Measurement meas1F = meas1; - final Measurement meas2F = meas2; - assertThrows(NotFoundException.class, () -> measurementDao.retrieveMeasurements(meas1F.getId().getOfficeId(), meas1F.getId().getName(), - null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null)); - assertThrows(NotFoundException.class, () -> measurementDao.retrieveMeasurements(meas2F.getId().getOfficeId(), meas2F.getId().getName(), - null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null)); - } finally { - //delete stream locations - streamLocationDao.deleteStreamLocation( - streamLocation.getStreamLocationNode().getId().getOfficeId(), - streamLocation.getStreamLocationNode().getStreamNode().getStreamId().getName(), - streamLocation.getStreamLocationNode().getId().getName() - ); - streamLocationDao.deleteStreamLocation( - streamLocation2.getStreamLocationNode().getId().getOfficeId(), - streamLocation2.getStreamLocationNode().getStreamNode().getStreamId().getName(), - streamLocation2.getStreamLocationNode().getId().getName() - ); - } - }, webUser); - } - - @Test - @MinimumSchema(MINIMUM_SCHEMA) - void testRoundTripMultipleStore() throws Exception { + void testRoundTripStore() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); String webUser = CwmsDataApiSetupCallback.getWebUser(); databaseLink.connection(c -> { diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/measurement.json b/cwms-data-api/src/test/resources/cwms/cda/api/measurement.json index 99f01da9e..0c23d00c6 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/api/measurement.json +++ b/cwms-data-api/src/test/resources/cwms/cda/api/measurement.json @@ -1,49 +1,51 @@ -{ - "height-unit": "ft", - "flow-unit": "cfs", - "temp-unit": "F", - "velocity-unit": "fps", - "area-unit": "ft2", - "used": true, - "agency": "USGS", - "party": "SPK", - "wm-comments": "Measurement made during normal flow conditions.", - "instant": "2024-09-16T00:00:00Z", - "number": "123456", - "id": { - "name": "StreamLoc321", - "office-id": "SPK" - }, - "streamflow-measurement": { - "gage-height": 5.5, - "flow": 250.0, - "quality": "G" - }, - "supplemental-streamflow-measurement": { - "channel-flow": 300.0, - "overbank-flow": 50.0, - "overbank-max-depth": 5.0, - "channel-max-depth": 10.0, - "avg-velocity": 1.5, - "surface-velocity": 2.0, - "max-velocity": 3.0, - "effective-flow-area": 200.0, - "cross-sectional-area": 250.0, - "mean-gage": 20.0, - "top-width": 30.0, - "main-channel-area": 150.0, - "overbank-area": 80.0 - }, - "usgs-measurement": { - "remarks": "Remarks", - "current-rating": "1", - "control-condition": "FILL", - "flow-adjustment": "OTHR", - "shift-used": 0.1, - "percent-difference": 5.0, - "delta-height": 0.05, - "delta-time": 10.0, - "air-temp": 20.0, - "water-temp": 15.0 +[ + { + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made during normal flow conditions.", + "instant": "2024-09-16T00:00:00Z", + "number": "123456", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 5.5, + "flow": 250.0, + "quality": "G" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 300.0, + "overbank-flow": 50.0, + "overbank-max-depth": 5.0, + "channel-max-depth": 10.0, + "avg-velocity": 1.5, + "surface-velocity": 2.0, + "max-velocity": 3.0, + "effective-flow-area": 200.0, + "cross-sectional-area": 250.0, + "mean-gage": 20.0, + "top-width": 30.0, + "main-channel-area": 150.0, + "overbank-area": 80.0 + }, + "usgs-measurement": { + "remarks": "Remarks", + "current-rating": "1", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.1, + "percent-difference": 5.0, + "delta-height": 0.05, + "delta-time": 10.0, + "air-temp": 20.0, + "water-temp": 15.0 + } } -} +] diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dao/dbMeasurement.xml b/cwms-data-api/src/test/resources/cwms/cda/data/dao/dbMeasurement.xml deleted file mode 100644 index 4f5e878d6..000000000 --- a/cwms-data-api/src/test/resources/cwms/cda/data/dao/dbMeasurement.xml +++ /dev/null @@ -1,40 +0,0 @@ - - 12345 - 2024-01-01T00:00:00Z - Walnut_Ck - SomeParty - Test comment - USGS - - 2.0 - 100.0 - good - - - Some remarks - 1 - UNSPECIFIED - 11.0 - 10.0 - UNKNOWN - 0.5 - 60.0 - 25.0 - 15.0 - - - 100.0 - 50.0 - 2.0 - 5.0 - 1.5 - 1.0 - 2.0 - 75.0 - 60.0 - 3.0 - 10.0 - 150.0 - 200.0 - - From bad6ba2c3c83c2941a68fafc05476b50206fa60f Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Thu, 17 Oct 2024 15:49:49 -0700 Subject: [PATCH 09/10] CWDB-225 - Added unit test for queryParamAsDouble in Controllers class. Made descriptions of date/timezones consistent. --- .../main/java/cwms/cda/api/Controllers.java | 3 -- .../cwms/cda/api/MeasurementController.java | 44 ++++++++++++++----- .../java/cwms/cda/api/ControllersTest.java | 21 +++++++++ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java index 52ce8a624..ba9e9035b 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java @@ -134,9 +134,6 @@ public final class Controllers { public static final String ISSUE_DATE = "issue-date"; public static final String LOCATION_KIND_LIKE = "location-kind-like"; public static final String LOCATION_TYPE_LIKE = "location-type-like"; - - public static final String MIN_DATE = "min-date"; - public static final String MAX_DATE = "max-date"; public static final String MIN_NUMBER = "min-number"; public static final String MAX_NUMBER = "max-number"; public static final String MIN_HEIGHT = "min-height"; diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java index cd0e6b12e..960985bea 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java @@ -28,17 +28,19 @@ import static com.codahale.metrics.MetricRegistry.name; import com.codahale.metrics.Timer; import static cwms.cda.api.Controllers.AGENCY; +import static cwms.cda.api.Controllers.BEGIN; import static cwms.cda.api.Controllers.CREATE; +import static cwms.cda.api.Controllers.DATE_FORMAT; import static cwms.cda.api.Controllers.DELETE; +import static cwms.cda.api.Controllers.EXAMPLE_DATE; 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.ID_MASK; import static cwms.cda.api.Controllers.LOCATION_ID; -import static cwms.cda.api.Controllers.MAX_DATE; +import static cwms.cda.api.Controllers.END; import static cwms.cda.api.Controllers.MAX_FLOW; import static cwms.cda.api.Controllers.MAX_HEIGHT; -import static cwms.cda.api.Controllers.MIN_DATE; import static cwms.cda.api.Controllers.MIN_FLOW; import static cwms.cda.api.Controllers.MIN_HEIGHT; import static cwms.cda.api.Controllers.NOT_SUPPORTED_YET; @@ -98,9 +100,18 @@ private Timer.Context markAndTime(String subject) { @OpenApiParam(name = ID_MASK, description = "Location id mask for filtering measurements. Use null to retrieve measurements for all locations."), @OpenApiParam(name = MIN_NUMBER, description = "Minimum measurement number-id for filtering measurements."), @OpenApiParam(name = MAX_NUMBER, description = "Maximum measurement number-id for filtering measurements."), - @OpenApiParam(name = MIN_DATE, description = "Minimum date-time for filtering measurements in ISO-8601 format."), - @OpenApiParam(name = MAX_DATE, description = "Maximum date-time for filtering measurements in ISO-8601 format."), - @OpenApiParam(name = TIMEZONE, description = "Timezone for the date range."), + @OpenApiParam(name = BEGIN, description = "The start of the time " + + "window to delete. The format for this field is ISO 8601 extended, with " + + "optional offset and timezone, i.e., '" + DATE_FORMAT + "', e.g., '" + + EXAMPLE_DATE + "'."), + @OpenApiParam(name = END, description = "The end of the time " + + "window to delete.The format for this field is ISO 8601 extended, with " + + "optional offset and timezone, i.e., '" + DATE_FORMAT + "', e.g., '" + + EXAMPLE_DATE + "'."), + @OpenApiParam(name = TIMEZONE, description = "This field specifies a default timezone " + + "to be used if the format of the " + BEGIN + "and " + END + + " parameters do not include offset or time zone information. " + + "Defaults to UTC."), @OpenApiParam(name = MIN_HEIGHT, description = "Minimum height for filtering measurements."), @OpenApiParam(name = MAX_HEIGHT, description = "Maximum height for filtering measurements."), @OpenApiParam(name = MIN_FLOW, description = "Minimum flow for filtering measurements."), @@ -128,8 +139,8 @@ public void getAll(@NotNull Context ctx) { String officeId = ctx.queryParam(OFFICE_MASK); String locationId = ctx.queryParam(ID_MASK); String unitSystem = ctx.queryParamAsClass(UNIT_SYSTEM, String.class).getOrDefault(UnitSystem.EN.value()); - Instant minDate = queryParamAsInstant(ctx, MIN_DATE); - Instant maxDate = queryParamAsInstant(ctx, MAX_DATE); + Instant minDate = queryParamAsInstant(ctx, BEGIN); + Instant maxDate = queryParamAsInstant(ctx, END); String minNum = ctx.queryParam(MIN_NUMBER); String maxNum = ctx.queryParam(MAX_NUMBER); Number minHeight = queryParamAsDouble(ctx, MIN_HEIGHT); @@ -217,9 +228,18 @@ public void update(@NotNull Context ctx, @NotNull String locationId) { @OpenApiParam(name = OFFICE, required = true, description = "Specifies the office of the measurements to delete"), @OpenApiParam(name = MIN_NUMBER, description = "Specifies the min number-id of the measurement to delete."), @OpenApiParam(name = MAX_NUMBER, description = "Specifies the max number-id of the measurement to delete."), - @OpenApiParam(name = MIN_DATE, description = "Specifies the minimum date (in ISO-8601 format) of the measurement to delete."), - @OpenApiParam(name = MAX_DATE, description = "Specifies the maximum date (in ISO-8601 format) of the measurement to delete."), - @OpenApiParam(name = TIMEZONE, description = "Specifies the timezone of the date range.") + @OpenApiParam(name = BEGIN, description = "The start of the time " + + "window to delete. The format for this field is ISO 8601 extended, with " + + "optional offset and timezone, i.e., '" + DATE_FORMAT + "', e.g., '" + + EXAMPLE_DATE + "'."), + @OpenApiParam(name = END, description = "The end of the time " + + "window to delete.The format for this field is ISO 8601 extended, with " + + "optional offset and timezone, i.e., '" + DATE_FORMAT + "', e.g., '" + + EXAMPLE_DATE + "'."), + @OpenApiParam(name = TIMEZONE, description = "This field specifies a default timezone " + + "to be used if the format of the " + BEGIN + "and " + END + + " parameters do not include offset or time zone information. " + + "Defaults to UTC."), }, description = "Delete an existing measurement.", method = HttpMethod.DELETE, @@ -234,8 +254,8 @@ public void delete(@NotNull Context ctx, @NotNull String locationId) { String officeId = requiredParam(ctx, OFFICE); String minNum = ctx.queryParam(MIN_NUMBER); String maxNum = ctx.queryParam(MAX_NUMBER); - Instant minDate = queryParamAsInstant(ctx, MIN_DATE); - Instant maxDate = queryParamAsInstant(ctx, MAX_DATE); + Instant minDate = queryParamAsInstant(ctx, BEGIN); + Instant maxDate = queryParamAsInstant(ctx, END); try (Timer.Context ignored = markAndTime(DELETE)) { DSLContext dsl = getDslContext(ctx); MeasurementDao dao = new MeasurementDao(dsl); diff --git a/cwms-data-api/src/test/java/cwms/cda/api/ControllersTest.java b/cwms-data-api/src/test/java/cwms/cda/api/ControllersTest.java index 2b07b7a2c..39a0c65f9 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/ControllersTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/ControllersTest.java @@ -413,4 +413,25 @@ void testRequiredParamAs() { assertThrows(RequiredQueryParameterException.class, () -> Controllers.requiredParamAs(ctx, Controllers.OFFICE, String.class)); } + + @Test + void testQueryParamAsDouble() { + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + Map urlParams = new LinkedHashMap<>(); + urlParams.put("a_double", "1.0"); + urlParams.put("an_int", "1"); + String paramStr = ControllerTest.buildParamStr(urlParams); + when(request.getQueryString()).thenReturn(paramStr); + Context ctx = new Context(request, response, new LinkedHashMap()); + + Double retVal = Controllers.queryParamAsDouble(ctx, "a_double"); + assertEquals(1.0, retVal); + + Double retVal2 = Controllers.queryParamAsDouble(ctx, "an_int"); + assertEquals(1.0, retVal2); + + Double retVal3 = Controllers.queryParamAsDouble(ctx, "null"); + assertNull(retVal3); + } } \ No newline at end of file From 8aa8f877b43c5ad6c0af4007efbf356b0a1123c8 Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Fri, 18 Oct 2024 15:29:18 -0700 Subject: [PATCH 10/10] CWDB-225 - Made time window required for deletion. Added description of null begin/end. --- .../java/cwms/cda/api/MeasurementController.java | 15 +++++++-------- .../java/cwms/cda/data/dao/MeasurementDao.java | 12 ++++++------ .../cwms/cda/api/MeasurementControllerTestIT.java | 3 +-- .../cwms/cda/data/dao/MeasurementDaoTestIT.java | 4 ++-- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java index 960985bea..8f2607176 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java @@ -103,11 +103,11 @@ private Timer.Context markAndTime(String subject) { @OpenApiParam(name = BEGIN, description = "The start of the time " + "window to delete. The format for this field is ISO 8601 extended, with " + "optional offset and timezone, i.e., '" + DATE_FORMAT + "', e.g., '" - + EXAMPLE_DATE + "'."), + + EXAMPLE_DATE + "'. A null value is treated as an unbounded start."), @OpenApiParam(name = END, description = "The end of the time " + "window to delete.The format for this field is ISO 8601 extended, with " + "optional offset and timezone, i.e., '" + DATE_FORMAT + "', e.g., '" - + EXAMPLE_DATE + "'."), + + EXAMPLE_DATE + "'.A null value is treated as an unbounded end."), @OpenApiParam(name = TIMEZONE, description = "This field specifies a default timezone " + "to be used if the format of the " + BEGIN + "and " + END + " parameters do not include offset or time zone information. " @@ -226,13 +226,11 @@ public void update(@NotNull Context ctx, @NotNull String locationId) { }, queryParams = { @OpenApiParam(name = OFFICE, required = true, description = "Specifies the office of the measurements to delete"), - @OpenApiParam(name = MIN_NUMBER, description = "Specifies the min number-id of the measurement to delete."), - @OpenApiParam(name = MAX_NUMBER, description = "Specifies the max number-id of the measurement to delete."), - @OpenApiParam(name = BEGIN, description = "The start of the time " + @OpenApiParam(name = BEGIN, required = true, description = "The start of the time " + "window to delete. The format for this field is ISO 8601 extended, with " + "optional offset and timezone, i.e., '" + DATE_FORMAT + "', e.g., '" + EXAMPLE_DATE + "'."), - @OpenApiParam(name = END, description = "The end of the time " + @OpenApiParam(name = END, required = true, description = "The end of the time " + "window to delete.The format for this field is ISO 8601 extended, with " + "optional offset and timezone, i.e., '" + DATE_FORMAT + "', e.g., '" + EXAMPLE_DATE + "'."), @@ -240,6 +238,8 @@ public void update(@NotNull Context ctx, @NotNull String locationId) { + "to be used if the format of the " + BEGIN + "and " + END + " parameters do not include offset or time zone information. " + "Defaults to UTC."), + @OpenApiParam(name = MIN_NUMBER, description = "Specifies the min number-id of the measurement to delete."), + @OpenApiParam(name = MAX_NUMBER, description = "Specifies the max number-id of the measurement to delete."), }, description = "Delete an existing measurement.", method = HttpMethod.DELETE, @@ -259,8 +259,7 @@ public void delete(@NotNull Context ctx, @NotNull String locationId) { try (Timer.Context ignored = markAndTime(DELETE)) { DSLContext dsl = getDslContext(ctx); MeasurementDao dao = new MeasurementDao(dsl); - dao.deleteMeasurements(officeId, locationId, minDate, maxDate, UnitSystem.EN.getValue(), null, - null, null, null, minNum, maxNum, null, null); + dao.deleteMeasurements(officeId, locationId, minDate, maxDate,minNum, maxNum); ctx.status(HttpServletResponse.SC_NO_CONTENT).json( "Measurements for " + locationId + " Deleted"); } } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java index 36ecd05bb..5aaac5932 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java @@ -136,19 +136,19 @@ private void storeMeasurementsJooq(Connection conn, List measuremen * * @param officeId - the office id * @param locationId - the location id of the measurement to delete + * @param minNum */ - public void deleteMeasurements(String officeId, String locationId, Instant minDateMask, Instant maxDateMask, String unitSystem, - Number minHeight, Number maxHeight, Number minFlow, Number maxFlow, String minNum, String maxNum, - String agencies, String qualities) { + public void deleteMeasurements(String officeId, String locationId, Instant minDateMask, Instant maxDateMask, String minNum, + String maxNum) { connection(dsl, conn -> { setOffice(conn, officeId); Timestamp minTimestamp = OracleTypeMap.buildTimestamp(minDateMask == null ? null : Date.from(minDateMask)); Timestamp maxTimestamp = OracleTypeMap.buildTimestamp(maxDateMask == null ? null : Date.from(maxDateMask)); TimeZone timeZone = OracleTypeMap.GMT_TIME_ZONE; String timeZoneId = timeZone.getID(); - verifyMeasurementsExists(conn, officeId, locationId, minNum, maxNum); - CWMS_STREAM_PACKAGE.call_DELETE_STREAMFLOW_MEAS(DSL.using(conn).configuration(), locationId, unitSystem, minTimestamp, maxTimestamp, - minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agencies, qualities, timeZoneId, officeId); + verifyMeasurementsExists(conn, officeId, locationId, maxNum, maxNum); + CWMS_STREAM_PACKAGE.call_DELETE_STREAMFLOW_MEAS(DSL.using(conn).configuration(), locationId, minNum, minTimestamp, maxTimestamp, + null, null, null, null, maxNum, maxNum, null, null, timeZoneId, officeId); }); } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index 5451340bf..aab51f262 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -47,7 +47,6 @@ import java.sql.SQLException; import java.time.Instant; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.servlet.http.HttpServletResponse; import mil.army.usace.hec.test.database.CwmsDatabaseContainer; @@ -119,7 +118,7 @@ public static void tearDown() { db.connection(c -> { MeasurementDao measDao = new MeasurementDao(getDslContext(c, OFFICE_ID)); try { - measDao.deleteMeasurements(OFFICE_ID, measLoc, null, null, null, null, null, null, null, null, null, null, null); + measDao.deleteMeasurements(OFFICE_ID, measLoc, null, null, null, null); } catch (Exception e) { LOGGER.atInfo().log("Failed to delete measurements for: " + measLoc + ". Measurement(s) likely already deleted"); } diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java index 50611c0b9..30331c1c7 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java @@ -152,8 +152,8 @@ void testRoundTripStore() throws Exception { DTOMatch.assertMatch(meas1B, retrievedMeas1B); //delete measurements - measurementDao.deleteMeasurements(meas1.getId().getOfficeId(), meas1.getId().getName(), null, null, null, null, null, null, null, null, null, null, null); - measurementDao.deleteMeasurements(meas2.getId().getOfficeId(), meas2.getId().getName(), null, null, null, null, null, null, null, null, null, null, null); + measurementDao.deleteMeasurements(meas1.getId().getOfficeId(), meas1.getId().getName(), null, null, null, null); + measurementDao.deleteMeasurements(meas2.getId().getOfficeId(), meas2.getId().getName(), null, null, null, null); final Measurement meas1F = meas1; final Measurement meas2F = meas2;