diff --git a/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java b/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java index d51bd0c87..6a65f4977 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java @@ -51,6 +51,7 @@ import static cwms.cda.api.Controllers.VERSION; import static cwms.cda.api.Controllers.addDeprecatedContentTypeWarning; import static cwms.cda.api.Controllers.queryParamAsClass; +import static cwms.cda.api.Controllers.queryParamAsZdt; import static cwms.cda.api.Controllers.requiredParam; import static cwms.cda.data.dao.JooqDao.getDslContext; @@ -67,7 +68,6 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import cwms.cda.api.enums.UnitSystem; -import cwms.cda.api.errors.CdaError; import cwms.cda.data.dao.LocationLevelsDao; import cwms.cda.data.dao.LocationLevelsDaoImpl; import cwms.cda.data.dto.LocationLevel; @@ -220,11 +220,10 @@ public void delete(@NotNull Context ctx, @NotNull String levelId) { + "specified or default units above the NGVD-29 datum."), @OpenApiParam(name = BEGIN, description = "Specifies the start of the time " + "window for data to be included in the response. If this field is " - + "not specified, any required time window begins 24 hours prior to " - + "the specified or default end time."), + + "not specified, no beginning time will be used."), @OpenApiParam(name = END, description = "Specifies the end of the time " + "window for data to be included in the response. If this field is " - + "not specified, any required time window ends at the current time"), + + "not specified, no end time will be used."), @OpenApiParam(name = TIMEZONE, description = "Specifies the time zone of " + "the values of the begin and end fields (unless otherwise " + "specified), as well as the time zone of any times in the response." @@ -280,23 +279,14 @@ public void getAll(@NotNull Context ctx) { boolean isLegacyVersion = version.equals("1"); - if (format.isEmpty() && !isLegacyVersion) - { + if (format.isEmpty() && !isLegacyVersion) { String cursor = ctx.queryParamAsClass(PAGE, String.class) .getOrDefault(""); int pageSize = ctx.queryParamAsClass(PAGE_SIZE, Integer.class) .getOrDefault(DEFAULT_PAGE_SIZE); - ZoneId tz = ZoneId.of(timezone, ZoneId.SHORT_IDS); - - ZonedDateTime endZdt = end != null ? DateUtils.parseUserDate(end, timezone) : - ZonedDateTime.now(tz); - ZonedDateTime beginZdt; - if (begin != null) { - beginZdt = DateUtils.parseUserDate(begin, timezone); - } else { - beginZdt = endZdt.minusHours(24); - } + ZonedDateTime endZdt = queryParamAsZdt(ctx, END); + ZonedDateTime beginZdt = queryParamAsZdt(ctx, BEGIN); LocationLevels levels = levelsDao.getLocationLevels(cursor, pageSize, levelIdMask, office, unit, datum, beginZdt, endZdt); @@ -315,12 +305,9 @@ public void getAll(@NotNull Context ctx) { ctx.status(HttpServletResponse.SC_OK); ctx.result(results); requestResultSize.update(results.length()); - if (isLegacyVersion) - { + if (isLegacyVersion) { ctx.contentType(contentType.toString()); - } - else - { + } else { ctx.contentType(contentType.getType()); } } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/LevelsControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/LevelsControllerTestIT.java index e0a37eddc..d275556e4 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/LevelsControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/LevelsControllerTestIT.java @@ -35,6 +35,7 @@ import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import org.jooq.DSLContext; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -44,6 +45,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.List; import java.util.NavigableMap; import java.util.TreeMap; @@ -59,6 +61,18 @@ public class LevelsControllerTestIT extends DataApiTestIT { public static final String OFFICE = "SPK"; + private final List levelList = new ArrayList<>(); + + @AfterEach + void cleanup() throws Exception { + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + DSLContext dsl = dslContext(c, OFFICE); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + for (LocationLevel level : levelList) { + dao.deleteLocationLevel(level.getLocationLevelId(), level.getLevelDate(), level.getOfficeId(), false); + } + }); + } @Test void test_location_level() throws Exception { @@ -70,11 +84,12 @@ void test_location_level() throws Exception { .withConstantValue(1.0) .withLevelUnitsId("ac-ft") .build(); - CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { - DSLContext dsl = dslContext(c, OFFICE); - LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); - dao.storeLocationLevel(level); - }); + levelList.add(level); + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + DSLContext dsl = dslContext(c, OFFICE); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + dao.storeLocationLevel(level); + }); //Read level without unit given() @@ -116,7 +131,110 @@ void test_location_level() throws Exception { .body("constant-value",equalTo(1.0F)); } + @Test + void test_retrieve_time_window() throws Exception { + createLocation("level_get_all_loc_1", true, OFFICE); + String levelId = "level_get_all_loc_1.Flow.Ave.1Day.Regulating"; + ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America/Los_Angeles")); + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + LocationLevel level = new LocationLevel.Builder(levelId, time) + .withOfficeId(OFFICE) + .withConstantValue(1.0) + .withLevelUnitsId("cms") + .build(); + levelList.add(level); + DSLContext dsl = dslContext(c, OFFICE); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + dao.storeLocationLevel(level); + }); + String locId2 = "level_get_all_loc_2"; + String levelId2 = locId2 + ".Stor.Ave.1Day.Regulating"; + createLocation(locId2, true, OFFICE); + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + + LocationLevel level = new LocationLevel.Builder(levelId2, time) + .withOfficeId(OFFICE) + .withConstantValue(2.0) + .withLevelUnitsId("ac-ft") + .build(); + levelList.add(level); + DSLContext dsl = dslContext(c, OFFICE); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + dao.storeLocationLevel(level); + }); + + //Read level with begin + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(LEVEL_ID_MASK, "level_get_all_loc_*") + .queryParam(BEGIN, "2020-06-01T00:00:00Z") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .assertThat() + .log().ifValidationFails(LogDetail.ALL, true) + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + + assertThat(response.path("levels.size()"),is(2)); + assertEquals(OFFICE, response.path("levels[0].office-id")); + assertEquals(levelId, response.path("levels[0].location-level-id")); + assertEquals("Regulating", response.path("levels[0].specified-level-id")); + assertEquals("Ave", response.path("levels[0].parameter-type-id")); + assertEquals("Flow", response.path("levels[0].parameter-id")); + assertEquals("cms", response.path("levels[0].level-units-id")); + assertEquals("2023-06-01T07:00:00Z", response.path("levels[0].level-date")); + assertEquals("1Day", response.path("levels[0].duration-id")); + assertEquals(OFFICE, response.path("levels[1].office-id")); + assertEquals(levelId2, response.path("levels[1].location-level-id")); + assertEquals("Regulating", response.path("levels[1].specified-level-id")); + assertEquals("Ave", response.path("levels[1].parameter-type-id")); + assertEquals("Stor", response.path("levels[1].parameter-id")); + assertEquals("m3", response.path("levels[1].level-units-id")); + assertEquals("2023-06-01T07:00:00Z", response.path("levels[1].level-date")); + assertEquals("1Day", response.path("levels[1].duration-id")); + + //Read level without begin and end + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(LEVEL_ID_MASK, "level_get_all_loc_*") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .assertThat() + .log().ifValidationFails(LogDetail.ALL, true) + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + + assertThat(response.path("levels.size()"),is(2)); + assertEquals(OFFICE, response.path("levels[0].office-id")); + assertEquals(levelId, response.path("levels[0].location-level-id")); + assertEquals("Regulating", response.path("levels[0].specified-level-id")); + assertEquals("Ave", response.path("levels[0].parameter-type-id")); + assertEquals("Flow", response.path("levels[0].parameter-id")); + assertEquals("cms", response.path("levels[0].level-units-id")); + assertEquals("2023-06-01T07:00:00Z", response.path("levels[0].level-date")); + assertEquals("1Day", response.path("levels[0].duration-id")); + assertEquals(OFFICE, response.path("levels[1].office-id")); + assertEquals(levelId2, response.path("levels[1].location-level-id")); + assertEquals("Regulating", response.path("levels[1].specified-level-id")); + assertEquals("Ave", response.path("levels[1].parameter-type-id")); + assertEquals("Stor", response.path("levels[1].parameter-id")); + assertEquals("m3", response.path("levels[1].level-units-id")); + assertEquals("2023-06-01T07:00:00Z", response.path("levels[1].level-date")); + assertEquals("1Day", response.path("levels[1].duration-id")); + } @Test void test_level_as_timeseries() throws Exception { @@ -132,6 +250,7 @@ void test_level_as_timeseries() throws Exception { .withConstantValue((double) i) .withLevelUnitsId("cfs") .build(); + levelList.add(level); levels.put(level.getLevelDate().toInstant(), level); CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { DSLContext dsl = dslContext(c, OFFICE); @@ -147,7 +266,7 @@ void test_level_as_timeseries() throws Exception { .accept(Formats.JSONV2) .contentType(Formats.JSONV2) .header("Authorization", user.toHeaderValue()) - .queryParam("office", OFFICE) + .queryParam(Controllers.OFFICE, OFFICE) .queryParam(BEGIN, time.toInstant().toString()) .queryParam(END, time.plusDays(effectiveDateCount).toInstant().toString()) .queryParam(INTERVAL, "1Hour") @@ -194,6 +313,7 @@ void test_get_all_location_level() throws Exception { .withConstantValue(1.0) .withLevelUnitsId("ac-ft") .build(); + levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); dao.storeLocationLevel(level); @@ -209,6 +329,7 @@ void test_get_all_location_level() throws Exception { .withConstantValue(2.0) .withLevelUnitsId("ac-ft") .build(); + levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); dao.storeLocationLevel(level); @@ -217,14 +338,12 @@ void test_get_all_location_level() throws Exception { String startStr = "2023-06-01T00:00:00Z"; String endStr = "2023-06-02T00:00:00Z"; - - //Read level without unit ExtractableResponse response = given() .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSONV2) .contentType(Formats.JSONV2) - .queryParam("office", OFFICE) + .queryParam(Controllers.OFFICE, OFFICE) .queryParam(LEVEL_ID_MASK, "level_get_all.*") .queryParam(BEGIN, startStr) .queryParam(END, endStr) @@ -266,7 +385,7 @@ void test_get_all_location_level() throws Exception { .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSONV2) .contentType(Formats.JSONV2) - .queryParam("office", OFFICE) + .queryParam(Controllers.OFFICE, OFFICE) .queryParam(UNIT, "SI") .queryParam(LEVEL_ID_MASK, "level_get_all.*") .queryParam(BEGIN, startStr) @@ -308,7 +427,7 @@ void test_get_all_location_level() throws Exception { .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSONV2) .contentType(Formats.JSONV2) - .queryParam("office", OFFICE) + .queryParam(Controllers.OFFICE, OFFICE) .queryParam(UNIT, "EN") .queryParam(LEVEL_ID_MASK, "level_get_all.*") .queryParam(BEGIN, startStr) @@ -345,14 +464,133 @@ void test_get_all_location_level() throws Exception { assertThat(response.path("levels[1].constant-value"), floatCloseTo(2.0, 0.01)); } + @Test + void test_get_all_earliest_time() throws Exception { + String locId = "level_get_all_loc1"; + String levelId = locId + ".Stor.Ave.1Day.Regulating"; + createLocation(locId, true, OFFICE); + final ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America" + + "/Los_Angeles")); + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + LocationLevel level = new LocationLevel.Builder(levelId, time) + .withOfficeId(OFFICE) + .withConstantValue(1.0) + .withLevelUnitsId("ac-ft") + .build(); + levelList.add(level); + DSLContext dsl = dslContext(c, OFFICE); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + dao.storeLocationLevel(level); + }); + + String locId2 = "level_get_all_loc2"; + String levelId2 = locId2 + ".Stor.Ave.1Day.Regulating"; + createLocation(locId2, true, OFFICE); + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + + LocationLevel level = new LocationLevel.Builder(levelId2, time) + .withOfficeId(OFFICE) + .withConstantValue(2.0) + .withLevelUnitsId("ac-ft") + .build(); + levelList.add(level); + DSLContext dsl = dslContext(c, OFFICE); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + dao.storeLocationLevel(level); + }); + + // Get all with minimum timestamp accepted by the database + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(LEVEL_ID_MASK, "level_get_all.*") + .queryParam(BEGIN, "-4712-11-25T00:00:00") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + + assertThat(response.path("levels.size()"),is(2)); + + assertThat(response.path("levels[0].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[0].location-level-id"),equalTo(levelId)); + assertThat(response.path("levels[0].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[0].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[0].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[0].level-units-id"),equalTo("m3")); + assertThat(response.path("levels[0].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[0].duration-id"),equalTo("1Day")); + double actual0 = Float.valueOf((float) response.path("levels[0].constant-value")).doubleValue(); + assertThat(actual0, closeTo(1233.0, 10.0)); + + assertThat(response.path("levels[1].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[1].location-level-id"),equalTo(levelId2)); + assertThat(response.path("levels[1].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[1].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[1].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[1].level-units-id"),equalTo("m3")); + assertThat(response.path("levels[1].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[1].duration-id"),equalTo("1Day")); + double actual1 = Float.valueOf((float) response.path("levels[1].constant-value")).doubleValue(); + assertThat(actual1, closeTo(2466.9636f, 1.0)); + + //Read level without time window + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(LEVEL_ID_MASK, "level_get_all.*") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + + assertThat(response.path("levels.size()"),is(2)); + + assertThat(response.path("levels[0].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[0].location-level-id"),equalTo(levelId)); + assertThat(response.path("levels[0].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[0].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[0].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[0].level-units-id"),equalTo("m3")); + assertThat(response.path("levels[0].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[0].duration-id"),equalTo("1Day")); + actual0 = Float.valueOf((float) response.path("levels[0].constant-value")).doubleValue(); + assertThat(actual0, closeTo(1233.0, 10.0)); + + assertThat(response.path("levels[1].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[1].location-level-id"),equalTo(levelId2)); + assertThat(response.path("levels[1].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[1].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[1].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[1].level-units-id"),equalTo("m3")); + assertThat(response.path("levels[1].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[1].duration-id"),equalTo("1Day")); + actual1 = Float.valueOf((float) response.path("levels[1].constant-value")).doubleValue(); + assertThat(actual1, closeTo(2466.9636f, 1.0)); + } + @ParameterizedTest @EnumSource(GetAllTestNewAliases.class) - void test_get_all_aliases_new(GetAllTestNewAliases test) throws Exception + void test_get_all_aliases_new(GetAllTestNewAliases test) { given() .log().ifValidationFails(LogDetail.ALL, true) .accept(test._accept) - .queryParam("office", OFFICE) + .queryParam(Controllers.OFFICE, OFFICE) .queryParam(LEVEL_ID_MASK, "level_get_all.*") .when() .redirects().follow(true) @@ -367,12 +605,12 @@ void test_get_all_aliases_new(GetAllTestNewAliases test) throws Exception @ParameterizedTest @EnumSource(GetAllTestLegacy.class) - void test_get_all_aliases_legacy(GetAllTestLegacy test) throws Exception + void test_get_all_aliases_legacy(GetAllTestLegacy test) { given() .log().ifValidationFails(LogDetail.ALL, true) .queryParam(FORMAT, test._format) - .queryParam("office", OFFICE) + .queryParam(Controllers.OFFICE, OFFICE) .queryParam(LEVEL_ID_MASK, "level_get_all.*") .when() .redirects()