diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeSeriesGroupDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeSeriesGroupDao.java index 724d2883e..45598cd39 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeSeriesGroupDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeSeriesGroupDao.java @@ -25,17 +25,24 @@ package cwms.cda.data.dao; import static com.google.common.flogger.LazyArgs.lazy; - import static java.util.stream.Collectors.toList; +import com.google.common.flogger.FluentLogger; import cwms.cda.data.dto.AssignedTimeSeries; import cwms.cda.data.dto.TimeSeriesCategory; import cwms.cda.data.dto.TimeSeriesGroup; import java.math.BigDecimal; import java.util.List; -import com.google.common.flogger.FluentLogger; import org.jetbrains.annotations.NotNull; -import org.jooq.*; +import org.jooq.Condition; +import org.jooq.Configuration; +import org.jooq.DSLContext; +import org.jooq.Record5; +import org.jooq.Record8; +import org.jooq.Record9; +import org.jooq.RecordMapper; +import org.jooq.SelectConditionStep; +import org.jooq.SelectSeekStep4; import org.jooq.conf.ParamType; import org.jooq.impl.DSL; import usace.cwms.db.jooq.codegen.packages.CWMS_TS_PACKAGE; @@ -189,11 +196,11 @@ private AssignedTimeSeries buildAssignedTimeSeries(Record5 categoriesToCleanup = new ArrayList<>(); - private List groupsToCleanup = new ArrayList<>(); - private List timeSeriesToCleanup = new ArrayList<>(); + private final List categoriesToCleanup = new ArrayList<>(); + private final List groupsToCleanup = new ArrayList<>(); + private final List timeSeriesToCleanup = new ArrayList<>(); private static final FluentLogger LOGGER = FluentLogger.forEnclosingClass(); TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; TestAccounts.KeyUser user2 = TestAccounts.KeyUser.SWT_NORMAL; @@ -1671,4 +1671,182 @@ void testRetrievalTiming() { .body("assigned-time-series.size()", greaterThan(0)) .time(lessThan(500L)); // should be pretty quick, under 0.5 seconds. Old query was ~3 seconds } + + @ParameterizedTest + @ValueSource(strings = {Formats.JSON, Formats.DEFAULT}) + void test_create_read_delete_null_attributes(String format) throws Exception { + String officeId = user.getOperatingOffice(); + String timeSeriesId = "Alder Springs.Precip-Cumulative.Inst.1Day.0.cda-attr"; + createLocation(timeSeriesId.split("\\.")[0],true, officeId); + TimeSeriesCategory cat = new TimeSeriesCategory(officeId, "test_attr", "NullTesting"); + TimeSeriesGroup group = new TimeSeriesGroup(cat, officeId, "test_attr", "NullTesting", + null, null); + List assignedTimeSeries = group.getAssignedTimeSeries(); + + assignedTimeSeries.add(new AssignedTimeSeries(officeId,timeSeriesId, null, null, 0)); + ContentType contentType = Formats.parseHeader(Formats.JSON, TimeSeriesCategory.class); + String categoryXml = Formats.format(contentType, cat); + String groupXml = Formats.format(contentType, group); + + //Create Category + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(format) + .contentType(Formats.JSON) + .body(categoryXml) + .header("Authorization", user.toHeaderValue()) + .queryParam(OFFICE, officeId) + .queryParam(FAIL_IF_EXISTS, false) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/timeseries/category") + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + + // insert the time series + createTimeseries(officeId, timeSeriesId, 0); + + //Create Group + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(format) + .contentType(Formats.JSON) + .body(groupXml) + .header("Authorization", user.toHeaderValue()) + .queryParam(FAIL_IF_EXISTS, false) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/timeseries/group") + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + + //Read + var tsGroup = given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(format) + .contentType(Formats.JSON) + .queryParam(OFFICE, officeId) + .queryParam(CATEGORY_OFFICE_ID, officeId) + .queryParam(GROUP_OFFICE_ID, officeId) + .queryParam(CATEGORY_ID, group.getTimeSeriesCategory().getId()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/timeseries/group/" + group.getId()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("office-id", equalTo(group.getOfficeId())) + .body("id", equalTo(group.getId())) + .body("description", equalTo(group.getDescription())) + .body("assigned-time-series[0].timeseries-id", equalTo(timeSeriesId)) + .extract(); + + var body = tsGroup.body().jsonPath().getList("assigned-time-series"); + for (Object o : body) { + String content = o.toString(); + assertFalse(content.contains("ref-ts-id")); + assertFalse(content.contains("ts-code")); + assertFalse(content.contains("alias-id")); + assertFalse(content.contains("null")); + } + + //Clear Assigned TS + group.getAssignedTimeSeries().clear(); + groupXml = Formats.format(contentType, group); + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(format) + .contentType(Formats.JSON) + .body(groupXml) + .header("Authorization", user.toHeaderValue()) + .queryParam(CATEGORY_ID, group.getTimeSeriesCategory().getId()) + .queryParam(REPLACE_ASSIGNED_TS, true) + .queryParam(OFFICE, group.getOfficeId()) + .when() + .redirects().follow(true) + .redirects().max(3) + .patch("/timeseries/group/"+ group.getId()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + //Delete timeseries + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(format) + .header("Authorization", user.toHeaderValue()) + .queryParam(OFFICE, officeId) + .queryParam(BEGIN, "2025-05-08T11:00:00+00:00") + .queryParam(END, "2025-05-19T11:00:00+00:00") + .queryParam("start-time-inclusive", "true") + .queryParam("end-time-inclusive", "true") + .queryParam("override-protection", "true") + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("/timeseries/" + timeSeriesId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + //Delete Group + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(format) + .contentType(Formats.JSON) + .header("Authorization", user.toHeaderValue()) + .queryParam(OFFICE, officeId) + .queryParam(CATEGORY_ID, cat.getId()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("/timeseries/group/" + group.getId()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)); + + //Read Empty + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(format) + .contentType(Formats.JSON) + .queryParam(OFFICE, officeId) + .queryParam(GROUP_OFFICE_ID, officeId) + .queryParam(CATEGORY_OFFICE_ID, officeId) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/timeseries/group/" + group.getId()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); + + //Delete Category + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(format) + .contentType(Formats.JSON) + .header("Authorization", user.toHeaderValue()) + .queryParam(OFFICE, officeId) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("/timeseries/category/" + group.getTimeSeriesCategory().getId()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)); + } } diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/TimeSeriesGroupTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/TimeSeriesGroupTest.java index 014e06d50..d099dcf4c 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/TimeSeriesGroupTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/TimeSeriesGroupTest.java @@ -1,10 +1,13 @@ package cwms.cda.data.dto; +import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.Test; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -30,17 +33,42 @@ void test_serialize_json(){ assertTrue(result.contains("grpSharedRefTsId")); } + @Test + void test_serialize_with_nulls() { + TimeSeriesGroup group = buildTimeSeriesGroup(); + List assignedTimeSeries = new ArrayList<>(); + AssignedTimeSeries timeSeries = new AssignedTimeSeries("SPK", + "BIG MUDDY.Elev.Total.1Day.1Day.CWMS", null, null, 0); + assignedTimeSeries.add(timeSeries); + TimeSeriesGroup groupWithNulls = new TimeSeriesGroup(group, assignedTimeSeries); + + ContentType contentType = Formats.parseHeader(Formats.JSON, TimeSeriesGroup.class); + String result = Formats.format(contentType, groupWithNulls); + assertNotNull(result); + + assertTrue(result.contains("catOfficeId")); + assertTrue(result.contains("catId")); + assertTrue(result.contains("catDesc")); + + assertTrue(result.contains("grpOfficeId")); + assertTrue(result.contains("grpId")); + assertTrue(result.contains("grpDesc")); + assertTrue(result.contains("grpSharedTsAliasId")); + assertTrue(result.contains("grpSharedRefTsId")); + + assertFalse(result.contains("null")); + } + private TimeSeriesGroup buildTimeSeriesGroup() { TimeSeriesCategory category = new TimeSeriesCategory( "catOfficeId", "catId", "catDesc" ); - TimeSeriesGroup retval = new TimeSeriesGroup(category, + + return new TimeSeriesGroup(category, "grpOfficeId", "grpId", "grpDesc", "grpSharedTsAliasId", "grpSharedRefTsId" ); - - return retval; } }