diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java index 99e9f3c22..aa7c55228 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java @@ -27,13 +27,13 @@ import static org.jooq.SQLDialect.ORACLE; import com.google.common.flogger.FluentLogger; -import com.google.common.flogger.StackSize; import cwms.cda.ApiServlet; import cwms.cda.api.errors.AlreadyExists; import cwms.cda.api.errors.FieldLengthExceededException; import cwms.cda.api.errors.InvalidItemException; import cwms.cda.api.errors.NotFoundException; import cwms.cda.datasource.ConnectionPreparingDataSource; +import cwms.cda.datasource.DelegatingConnectionPreparer; import cwms.cda.helpers.DatabaseHelpers.SCHEMA_VERSION; import cwms.cda.security.CwmsAuthException; import io.javalin.http.Context; @@ -72,9 +72,7 @@ import org.jooq.impl.DefaultExecuteListenerProvider; import org.owasp.html.HtmlPolicyBuilder; import org.owasp.html.PolicyFactory; -import usace.cwms.db.jooq.codegen.packages.CWMS_ENV_PACKAGE; import usace.cwms.db.jooq.codegen.packages.CWMS_LOC_PACKAGE; -import usace.cwms.db.jooq.codegen.packages.CWMS_UTIL_PACKAGE; public abstract class JooqDao extends Dao { @@ -89,7 +87,7 @@ public abstract class JooqDao extends Dao { static ExecuteListener listener = new ExceptionWrappingListener(); private static final Pattern INVALID_OFFICE_ID = Pattern.compile( - "INVALID_OFFICE_ID: \"([^\"]+)\" is not a valid CWMS office id"); + "INVALID_OFFICE_ID: \"([^\"]+)\" is not a valid CWMS office id"); private static final Pattern INVALID_UNIT = Pattern.compile( "(.+\\R+){6}ORA-20102: The unit: \\S+" + " is not a recognized CWMS Database unit for the .+(.+\\R+){10}"); @@ -135,34 +133,22 @@ protected JooqDao(DSLContext dsl) { */ public static DSLContext getDslContext(Context ctx) { DSLContext retVal; - final String officeId = ctx.attribute(ApiServlet.OFFICE_ID); + final DataSource dataSource = ctx.attribute(ApiServlet.DATA_SOURCE); - final Boolean isNewLRTS = ctx.header(ApiServlet.IS_NEW_LRTS) == null - ? null : Boolean.parseBoolean(ctx.header(ApiServlet.IS_NEW_LRTS)); + final boolean isNewLRTS = ctx.header(ApiServlet.IS_NEW_LRTS) != null && Boolean.parseBoolean(ctx.header(ApiServlet.IS_NEW_LRTS)); - if (dataSource != null) { - DataSource wrappedDataSource = new ConnectionPreparingDataSource(connection -> - setClientInfo(ctx, connection), dataSource); - retVal = DSL.using(wrappedDataSource, SQLDialect.ORACLE18C); - } else { - // Some tests still use this method - logger.atFine().withStackTrace(StackSize.FULL) - .log("System still using old context method."); - Connection database = ctx.attribute(ApiServlet.DATABASE); - retVal = getDslContext(database, officeId); - } + DelegatingConnectionPreparer preparer = new DelegatingConnectionPreparer( + connection -> setClientInfo(ctx, connection), + new cwms.cda.datasource.LrtsSessionPreparer(isNewLRTS)); + DataSource wrappedDataSource = new ConnectionPreparingDataSource(preparer, dataSource); + retVal = DSL.using(wrappedDataSource, SQLDialect.ORACLE18C); retVal.configuration().set(new DefaultExecuteListenerProvider(listener)); - if (isNewLRTS != null) { - String requireBool = isNewLRTS ? "T" : "F"; - int requireIntValue = isNewLRTS ? REQUIRE_NEW_LRTS_ID_FORMAT : REQUIRE_OLD_LRTS_ID_FORMAT; - CWMS_UTIL_PACKAGE.call_SET_SESSION_INFO(retVal.configuration(), - SESSION_USE_LRTS_ID_FORMAT, requireBool, requireIntValue); - } return retVal; } + protected static Timestamp buildTimestamp(Instant date) { return date != null ? Timestamp.from(date) : null; } @@ -186,8 +172,8 @@ private static Connection setClientInfo(Context ctx, Connection connection) { try { final String apiVersion = ApiServlet.getApiVersion(); connection.setClientInfo("OCSID.ECID", - ApiServlet.APPLICATION_TITLE + " " + - apiVersion.substring(0,Math.min(ORACLE_ECID_MAX_LENGTH,apiVersion.length()))); + ApiServlet.APPLICATION_TITLE + " " + + apiVersion.substring(0, Math.min(ORACLE_ECID_MAX_LENGTH, apiVersion.length()))); if (ctx.handlerType() == HandlerType.BEFORE) { connection.setClientInfo("OCSID.MODULE", "BEFORE-HANDLER"); } else { @@ -219,10 +205,11 @@ protected static Double toDouble(BigDecimal bigDecimal) { /** * The idea here is that this will check the current default datum, - * possible switch to the specified datum and - * then run the code and - * if the datum was previously switched - * then switch back to the initial datum. + * possible switch to the specified datum and + * then run the code and + * if the datum was previously switched + * then switch back to the initial datum. + * * @param targetDatum The desired ver * @param dslContext * @param cr @@ -251,7 +238,7 @@ protected void withDefaultDatum(@Nullable VerticalDatum targetDatum, DSLContext * an easy to read manner without having to worry about the syntax. */ public static Condition caseInsensitiveLikeRegex(Field field, String regex) { - if("*".equals(regex) || ".*".equals(regex)) { + if ("*".equals(regex) || ".*".equals(regex)) { return DSL.noCondition(); } return new CustomCondition() { @@ -302,6 +289,7 @@ public static Condition caseInsensitiveLikeRegexNullTrue(Field field, St * is one of several types of exception (e.q NotFound, * AlreadyExists, NullArg) that can be specially handled by ApiServlet * by returning specific HTTP codes or error messages. + * * @param input the observed exception * @return An exception, possibly wrapped */ @@ -360,7 +348,7 @@ private static boolean hasCodeOrMessage(SQLException sqlException, } private static boolean hasCodeAndMessage(SQLException sqlException, - List codes, List segments) { + List codes, List segments) { final String localizedMessage = sqlException.getLocalizedMessage(); return codes.contains(sqlException.getErrorCode()) @@ -394,26 +382,26 @@ public static boolean isNotFound(RuntimeException input) { public static boolean isInvalidOffice(RuntimeException input) { return getSqlException(input) - .map(sqlException -> hasCodeOrMessage(sqlException, Collections.singletonList(20010), - Collections.singletonList("INVALID_OFFICE_ID"))) - .orElse(false); + .map(sqlException -> hasCodeOrMessage(sqlException, Collections.singletonList(20010), + Collections.singletonList("INVALID_OFFICE_ID"))) + .orElse(false); } public static boolean isValueTooLargeException(RuntimeException input) { return getSqlException(input.getCause()).map(sqlException -> hasCodeOrMessage(sqlException, - Arrays.asList(6502, 12899, 20041), - Arrays.asList("value too large for column", "character string buffer too small", - "Error while writing value at JDBC bind index:"))) - .orElse(false); + Arrays.asList(6502, 12899, 20041), + Arrays.asList("value too large for column", "character string buffer too small", + "Error while writing value at JDBC bind index:"))) + .orElse(false); } public static boolean isTSIDInvalidIntervalException(RuntimeException input) { return getSqlException(input.getCause()).map(sqlException -> hasCodeAndMessage(sqlException, - Arrays.asList(20205, 20998), - Arrays.asList("Invalid Time Series Description", - "is not a valid interval", - "INVALID Time Series Identifier"))) - .orElse(false); + Arrays.asList(20205, 20998), + Arrays.asList("Invalid Time Series Description", + "is not a valid interval", + "INVALID Time Series Identifier"))) + .orElse(false); } static InvalidItemException buildInvalidTSIDIntervalException(RuntimeException input) { @@ -428,12 +416,11 @@ static InvalidItemException buildInvalidTSIDIntervalException(RuntimeException i if (localizedMessage != null) { String[] parts = localizedMessage.split("\n"); String errorMessage = parts[0]; - if (CURRENT_SCHEMA_VERSION <= SCHEMA_VERSION.V2025_07_01.numeric() && parts.length > 2) - { + if (CURRENT_SCHEMA_VERSION <= SCHEMA_VERSION.V2025_07_01.numeric() && parts.length > 2) { errorMessage = parts[1]; } - return new InvalidItemException(String.format("Invalid time series description: %s", - errorMessage), cause); + return new InvalidItemException(String.format("Invalid time series description: %s", + errorMessage), cause); } return new InvalidItemException("Invalid time series description", cause); } @@ -502,7 +489,7 @@ static CwmsAuthException buildNotAuthorizedForOffice(RuntimeException input) { } return new CwmsAuthException("User not authorized for this office.", cause, - HttpServletResponse.SC_UNAUTHORIZED); + HttpServletResponse.SC_UNAUTHORIZED); } public static boolean isAlreadyExists(RuntimeException input) { @@ -728,8 +715,8 @@ public static boolean isUnsupportedOperationException(RuntimeException input) { int errorCode = sqlException.getErrorCode(); //procedure doesn't exist retVal = errorCode == 904 - //Table or view does not exist - || errorCode == 942; + //Table or view does not exist + || errorCode == 942; } return retVal; } @@ -764,8 +751,9 @@ private static UnsupportedOperationException buildUnsupportedOperationException( /** * JooqDao provides its own connection() which wraps throw exceptions * because the DSL.connection() method does not wrap exceptions. + * * @param dslContext the DSLContext to use - * @param cr the ConnectionRunnable to run with the connection + * @param cr the ConnectionRunnable to run with the connection */ protected static void connection(DSLContext dslContext, ConnectionRunnable cr) { try { @@ -779,8 +767,9 @@ protected static void connection(DSLContext dslContext, ConnectionRunnable cr) { * Like DSL.connection the DSL.connectionResult method does not cause thrown * exceptions to be wrapped. This method delegates to DSL.connectionResult * but will wrap exceptions into more specific exception types where possible. + * * @param dslContext the DSLContext to use - * @param var1 the ConnectionCallable to run with the connection + * @param var1 the ConnectionCallable to run with the connection */ protected static R connectionResult(DSLContext dslContext, ConnectionCallable var1) { try { @@ -808,7 +797,7 @@ protected static ZoneId toZoneId(String zoneId, String locationId) { } else { if (logger.atFine().isEnabled()) { logger.atWarning().withCause(e) - .log("Location %s has an invalid location time zone: %s", locationId, zoneId); + .log("Location %s has an invalid location time zone: %s", locationId, zoneId); } else { logger.atWarning().log("Location %s has an invalid location time zone: %s", locationId, zoneId); } @@ -820,7 +809,7 @@ protected static ZoneId toZoneId(String zoneId, String locationId) { public static BigDecimal toBigDecimal(Number number) { return (number == null) ? null : BigDecimal.valueOf( - number.doubleValue()); + number.doubleValue()); } public static double buildDouble(BigDecimal bigDecimal) { @@ -828,7 +817,7 @@ public static double buildDouble(BigDecimal bigDecimal) { } protected static void checkMetaData(ResultSetMetaData metaData, List columnList, - String type) throws SQLException { + String type) throws SQLException { int columnCount = metaData.getColumnCount(); List metadataColumns = new ArrayList<>(); logger.atFine().log("{0} column dump.", type); diff --git a/cwms-data-api/src/main/java/cwms/cda/datasource/LrtsSessionPreparer.java b/cwms-data-api/src/main/java/cwms/cda/datasource/LrtsSessionPreparer.java new file mode 100644 index 000000000..0342cccfc --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/datasource/LrtsSessionPreparer.java @@ -0,0 +1,100 @@ +package cwms.cda.datasource; + +import static cwms.cda.data.dao.Dao.formatBool; +import static org.jooq.SQLDialect.ORACLE18C; + +import com.google.common.flogger.FluentLogger; +import cwms.cda.data.dao.JooqDao; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import org.jooq.DSLContext; +import org.jooq.impl.DSL; +import usace.cwms.db.jooq.codegen.packages.CWMS_UTIL_PACKAGE; + + +/** + * Prepares a connection by setting the LRTS session flag. + */ +public class LrtsSessionPreparer implements ConnectionPreparer { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private final Boolean isNewLrts; + private final boolean clearOnClose; + + public LrtsSessionPreparer(Boolean isNewLrts) { + this(isNewLrts, false); + } + + public LrtsSessionPreparer(Boolean isNewLrts, boolean clear) { + this.isNewLrts = isNewLrts; + clearOnClose = clear; + } + + @Override + public Connection prepare(Connection connection) { + if (isNewLrts == null) { + return connection; + } + + DSLContext dsl = DSL.using(connection, ORACLE18C); + + // Can also get current value and remember it and then reset to that in the close + // if setting with null,null doesn't work. + // GET_SESSION_INFO sessionInfo = CWMS_UTIL_PACKAGE.call_GET_SESSION_INFO(dslContext.configuration() + // , SESSION_USE_LRTS_ID_FORMAT); + + String requireBool = formatBool(isNewLrts); + int requireIntValue = isNewLrts ? JooqDao.REQUIRE_NEW_LRTS_ID_FORMAT + : JooqDao.REQUIRE_OLD_LRTS_ID_FORMAT; + CWMS_UTIL_PACKAGE.call_SET_SESSION_INFO(dsl.configuration(), + JooqDao.SESSION_USE_LRTS_ID_FORMAT, requireBool, requireIntValue); + logger.atFine().log("Set LRTS session flag to %s (%d) for connection %s", + requireBool, requireIntValue, connection); + + if (clearOnClose) { + // Return a proxy that will unset the flag on close() + return (Connection) Proxy.newProxyInstance( + connection.getClass().getClassLoader(), + new Class[]{Connection.class}, + new CloseUnsettingHandler(connection)); + } else { + return connection; + } + + } + + private static class CloseUnsettingHandler implements InvocationHandler { + private final Connection delegate; + private volatile boolean closed = false; + + CloseUnsettingHandler(Connection delegate) { + this.delegate = delegate; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String name = method.getName(); + if ("close".equals(name)) { + if (!closed) { + try { + DSLContext dsl = DSL.using(delegate, ORACLE18C); + // Passing null values to clear the session setting + CWMS_UTIL_PACKAGE.call_SET_SESSION_INFO(dsl.configuration(), + JooqDao.SESSION_USE_LRTS_ID_FORMAT, null, null); + logger.atFine().log("Cleared LRTS session flag for connection %s", delegate); + } catch (RuntimeException ex) { + logger.atWarning().withCause(ex) + .log("Failed to clear LRTS session flag on connection close"); + } finally { + closed = true; + } + } + return method.invoke(delegate, args); + } + return method.invoke(delegate, args); + } + } +} diff --git a/cwms-data-api/src/test/java/cwms/cda/api/DataApiTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/DataApiTestIT.java index bb213b636..0585da65d 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/DataApiTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/DataApiTestIT.java @@ -29,20 +29,25 @@ import com.atlassian.oai.validator.restassured.OpenApiValidationFilter; import com.google.common.flogger.FluentLogger; - import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.StreamDao; -import cwms.cda.data.dao.basin.BasinDao; import cwms.cda.data.dao.VerticalDatum; +import cwms.cda.data.dao.basin.BasinDao; import cwms.cda.data.dto.Location; import cwms.cda.data.dto.LocationCategory; import cwms.cda.data.dto.LocationGroup; import cwms.cda.data.dto.basin.Basin; import cwms.cda.data.dto.stream.Stream; import cwms.cda.helpers.ZoneIdHelper; -import fixtures.*; +import fixtures.CwmsDataApiSetupCallback; +import fixtures.IntegrationTestNameGenerator; +import fixtures.KeyCloakExtension; +import fixtures.MinIOExtension; +import fixtures.TestAccounts; import fixtures.users.MockCwmsUserPrincipalImpl; - +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; import java.io.File; import java.io.IOException; import java.io.StringWriter; @@ -60,17 +65,12 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; - import mil.army.usace.hec.test.database.CwmsDatabaseContainer; import org.apache.catalina.Manager; import org.apache.catalina.SessionEvent; import org.apache.catalina.SessionListener; import org.apache.catalina.session.StandardSession; import org.apache.commons.io.IOUtils; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; - import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.jooq.impl.DSL; @@ -83,7 +83,6 @@ import usace.cwms.db.jooq.codegen.packages.CWMS_ENV_PACKAGE; import usace.cwms.db.jooq.codegen.packages.CWMS_LOC_PACKAGE; import usace.cwms.db.jooq.codegen.packages.CWMS_UTIL_PACKAGE; -import usace.cwms.db.jooq.codegen.tables.AV_VERT_DATUM_OFFSET; /** * Helper class to manage cycling tests multiple times against a database. @@ -95,7 +94,7 @@ @ExtendWith(MinIOExtension.class) @ExtendWith(CwmsDataApiSetupCallback.class) public class DataApiTestIT { - private static FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); protected static String createLocationQuery = null; protected static String createTimeseriesQuery = null; @@ -652,7 +651,7 @@ protected void registerCategory(LocationCategory category) { @AfterEach public void cleanupLocationGroups() throws Exception { if (this.groupsCreated.isEmpty()) { - logger.atInfo().log("No groups to cleanup."); + logger.atFine().log("No groups to cleanup."); return; } logger.atInfo().log("Cleaning up groups that tests did not remove."); @@ -677,7 +676,7 @@ public void cleanupLocationGroups() throws Exception { @AfterEach public void cleanupLocationCategories() throws Exception { if (this.categoriesCreated.isEmpty()) { - logger.atInfo().log("No location categories to cleanup."); + logger.atFine().log("No location categories to cleanup."); return; } logger.atInfo().log("Cleaning up location categories that tests did not remove."); @@ -707,7 +706,7 @@ public void cleanupLocationCategories() throws Exception { */ public static void cleanupBasins() throws Exception { if (basinsCreated.isEmpty()) { - logger.atInfo().log("No basins to cleanup."); + logger.atFine().log("No basins to cleanup."); return; } logger.atInfo().log("Cleaning up basins test did not remove."); diff --git a/cwms-data-api/src/test/java/cwms/cda/api/LocationControllerTest.java b/cwms-data-api/src/test/java/cwms/cda/api/LocationControllerTest.java deleted file mode 100644 index 4483ec70b..000000000 --- a/cwms-data-api/src/test/java/cwms/cda/api/LocationControllerTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package cwms.cda.api; - -import java.util.HashMap; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.codahale.metrics.MetricRegistry; - -import cwms.cda.api.enums.Nation; -import cwms.cda.data.dto.Location; -import cwms.cda.formatters.Formats; -import fixtures.TestHttpServletResponse; -import fixtures.TestServletInputStream; -import io.javalin.http.Context; -import io.javalin.http.HandlerType; -import io.javalin.http.util.ContextUtil; -import io.javalin.plugin.json.JavalinJackson; -import io.javalin.plugin.json.JsonMapperKt; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class LocationControllerTest extends ControllerTest -{ - @Test - void testDeserializeLocationXml() - { - String xml = loadResourceAsString("cwms/cda/api/location_create.xml"); - assertNotNull(xml); - Location location = Formats.parseContent(Formats.parseHeader(Formats.XML, Location.class), - xml, Location.class); - assertNotNull(location); - assertEquals("LOC_TEST", location.getName()); - assertEquals("LRL", location.getOfficeId()); - assertEquals("NGVD-29", location.getHorizontalDatum()); - assertEquals("UTC", location.getTimezoneName()); - assertEquals(Nation.US, location.getNation()); - } - - @Test - void testDeserializeLocationJSON() - { - final String OFFICE = "SPK"; - final String json = loadResourceAsString("cwms/cda/api/location_create_spk.json"); - - assertNotNull(json); - Location location = Formats.parseContent(Formats.parseHeader(Formats.JSON, Location.class), - json, Location.class); - assertNotNull(location); - assertEquals("LOC_TEST", location.getName()); - assertEquals(OFFICE, location.getOfficeId()); - assertEquals("NGVD-29", location.getHorizontalDatum()); - assertEquals("UTC", location.getTimezoneName()); - assertEquals(Nation.US, location.getNation()); - } - - /** - * Test of getOne method, of class LocationController. - */ - @Test - void test_basic_operations() throws Exception { - final String testBody = ""; - LocationController instance = new LocationController(new MetricRegistry()); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = new TestHttpServletResponse(); - HashMap attributes = new HashMap<>(); - attributes.put(ContextUtil.maxRequestSizeKey,Integer.MAX_VALUE); - attributes.put(JsonMapperKt.JSON_MAPPER_KEY,new JavalinJackson()); - - when(request.getInputStream()).thenReturn(new TestServletInputStream(testBody)); - when(request.getQueryString()).thenReturn("office=LRL"); - - final Context context = ContextUtil.init(request,response,"*", new HashMap<>(), HandlerType.GET,attributes); - context.attribute("database",getTestConnection()); - - when(request.getAttribute("database")).thenReturn(getTestConnection()); - - assertNotNull( context.attribute("database"), "could not get the connection back as an attribute"); - assertNotNull(context.queryParam("office"), "could not get the office back as a query parameter" ); - - String locationId = "SimpleNoAlias"; - - instance.getOne(context, locationId); - assertEquals(200,context.status(), "incorrect status code returned"); - } -} diff --git a/cwms-data-api/src/test/java/cwms/cda/api/TimeseriesControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/TimeseriesControllerTestIT.java index 961a5ae92..f7afee89f 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/TimeseriesControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/TimeseriesControllerTestIT.java @@ -1,11 +1,29 @@ package cwms.cda.api; -import static cwms.cda.api.Controllers.*; +import static cwms.cda.api.Controllers.BEGIN; +import static cwms.cda.api.Controllers.CREATE_AS_LRTS; +import static cwms.cda.api.Controllers.DATUM; +import static cwms.cda.api.Controllers.END; +import static cwms.cda.api.Controllers.END_TIME_INCLUSIVE; +import static cwms.cda.api.Controllers.INCLUDE_ENTRY_DATE; +import static cwms.cda.api.Controllers.INCLUDE_EXTENTS; +import static cwms.cda.api.Controllers.NAME; +import static cwms.cda.api.Controllers.OFFICE; +import static cwms.cda.api.Controllers.OVERRIDE_PROTECTION; +import static cwms.cda.api.Controllers.START_TIME_INCLUSIVE; +import static cwms.cda.api.Controllers.TRIM; +import static cwms.cda.api.Controllers.UNIT; +import static cwms.cda.api.Controllers.VERSION_DATE; import static cwms.cda.data.dao.JooqDao.getDslContext; import static helpers.FloatCloseTo.floatCloseTo; import static io.restassured.RestAssured.given; import static io.restassured.config.JsonConfig.jsonConfig; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -19,23 +37,12 @@ import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; import cwms.cda.helpers.DatabaseHelpers.SCHEMA_VERSION; -import cwms.cda.helpers.ZoneIdHelper; import fixtures.CwmsDataApiSetupCallback; import fixtures.MinimumSchema; import fixtures.TestAccounts; import io.restassured.RestAssured; import io.restassured.filter.log.LogDetail; import io.restassured.path.json.config.JsonPathConfig; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.servlet.http.HttpServletResponse; - import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import io.restassured.response.ValidatableResponse; @@ -49,6 +56,16 @@ import org.junit.jupiter.params.provider.EnumSource; import usace.cwms.db.jooq.codegen.packages.CWMS_LOC_PACKAGE; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + @Tag("integration") final class TimeseriesControllerTestIT extends DataApiTestIT { public static final int MINIMUM_SCHEMA = 999999; @@ -692,6 +709,7 @@ void test_lrl_timeseries_lrts_reg1week() throws Exception { .contentType(Formats.JSONV2) .body(tsDataPsuedoOff) .header("Authorization",user.toHeaderValue()) + .header(ApiServlet.IS_NEW_LRTS, false) .queryParam("office",officeId) .when() .redirects().follow(true) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTest.java b/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTest.java deleted file mode 100644 index 1dd831ede..000000000 --- a/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTest.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2025 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.rating; - -import static io.restassured.RestAssured.given; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.codahale.metrics.MetricRegistry; - -import cwms.cda.data.dao.DaoTest; -import cwms.cda.data.dao.JsonRatingUtils; -import cwms.cda.data.dao.JsonRatingUtilsTest; -import cwms.cda.formatters.Formats; -import fixtures.TestServletInputStream; -import hec.data.cwmsRating.RatingSet; -import io.javalin.core.util.Header; -import io.javalin.http.Context; -import io.javalin.http.HandlerType; -import io.javalin.http.util.ContextUtil; -import io.javalin.plugin.json.JavalinJackson; -import io.javalin.plugin.json.JsonMapperKt; -import io.restassured.response.Response; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.util.HashMap; -import com.google.common.flogger.FluentLogger; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.sql.DataSource; -import mil.army.usace.hec.cwms.rating.io.xml.RatingXmlFactory; -import org.jetbrains.annotations.Nullable; -import org.jooq.tools.jdbc.MockConnection; -import org.jooq.tools.jdbc.MockFileDatabase; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - - -class RatingsControllerTest { - - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - private DataSource ds = mock(DataSource.class); - private Connection conn = null; - - - @BeforeEach - public void baseLineDbMocks() throws IOException{ - InputStream stream = RatingsControllerTest.class.getResourceAsStream("/ratings_db.txt"); - assertNotNull(stream); - this.conn = new MockConnection(new MockFileDatabase(stream)); - assertNotNull(this.conn, "Connection is null; something has gone wrong with the fixture setup"); - } - - - // This is only supposed to test that when XML data is posted to create, - // that data is forward to the method deserializeFromXml - @Test - void post_to_create_passed_to_deserializeXml() throws Exception - { - final String testBody = "could be anything"; - - - RatingController controller = spy(new RatingController(new MetricRegistry())); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - HashMap attributes = new HashMap<>(); - attributes.put(ContextUtil.maxRequestSizeKey,Integer.MAX_VALUE); - attributes.put(JsonMapperKt.JSON_MAPPER_KEY,new JavalinJackson()); - - when(request.getInputStream()).thenReturn(new TestServletInputStream(testBody)); - //Context context = new Context(request,response, attributes); - Context context = ContextUtil.init(request,response,"*",new HashMap<>(), HandlerType.POST,attributes); - context.attribute("database",this.conn); - - when(request.getContentLength()).thenReturn(testBody.length()); - when(request.getAttribute("database")).thenReturn(this.conn); - - assertNotNull( context.attribute("database"), "could not get the connection back as an attribute"); - - when(request.getHeader(Header.ACCEPT)).thenReturn(Formats.XMLV2); - when(request.getContentType()).thenReturn(Formats.XMLV2); - - logger.atInfo().log("Test post_to_create_passed_to_deserializeXml may trigger a RatingException - this is fine."); - try { - controller.create(context); - } catch (IllegalArgumentException e){ - logger.atInfo().log("Test post_to_create_passed_to_deserializeXml caught an IllegalArgumentException - this is fine."); - } - // For this test, it's ok that the server throws a RatingException - // Only want to check that the controller accessed our mock dao in the expected way - verify(controller, times(1)).deserializeRatingSet(testBody, Formats.XML, true); // Curious that it is XML and not XMLv2 - - } - - @Disabled("incomplete") - @Test - void retrieve_create_retrieve_delete_retrieve_json() throws Exception{ - // Do whatever we need to do to startup the server - int port = 7000; - DataSource testDS = buildDataSource(); - String baseUri = "http://localhost:" + port; - //// Todo: there needs to be some sort of Tomcat start here. - try - { - // Read in a resource and build our test data - String resourcePath = "cwms/cda/data/dao/BEAV.Stage_Flow.BASE.PRODUCTION.xml"; - String refRating = JsonRatingUtilsTest.loadResourceAsString(resourcePath); - assertNotNull(refRating); - RatingSet refRatingSet = RatingXmlFactory.ratingSet(refRating); - String office = "SWT"; - - String refSpecId = refRatingSet.getName(); - String refRatingJson = JsonRatingUtils.toJson(refRatingSet); - String newLoc = "TESTLOC"; - - String testSpecId = refSpecId.replace("BEAV", newLoc); - String testRatingJson = refRatingJson.replace("BEAV", newLoc).replace("Beaver", "TestLoc"); - - try - { - // Make sure we can't find the new rating - Response missingReponse = given() - .baseUri(baseUri) - .accept("application/json;version=2") - .param("office", office) - .when() - .get("/ratings/" + testSpecId) - .then() - .extract() - .response(); - Assertions.assertEquals(404, missingReponse.statusCode()); - // Cool, it's not there. - - // Now lets create it from json. - Response createReponse = given() - .baseUri(baseUri) - .body(testRatingJson) - .accept("application/json;version=2") - .when() - .post("/ratings") - .then() - .extract() - .response(); - Assertions.assertEquals(200, createReponse.statusCode()); - // Cool, created it. - - // Now lets get it - Response secondGetReponse = given() - .baseUri(baseUri) - .accept("application/json;version=2") - .param("office", office) - .when() - .get("/ratings/" + testSpecId) - .then() - .extract() - .response(); - Assertions.assertEquals(200, secondGetReponse.statusCode()); - // Cool, got it. - } - finally - { - // Now lets delete it - Response deleteReponse = given() - .baseUri(baseUri) - .accept("application/json;version=2") - .param("office", office) - .when() - .delete("/ratings/" + testSpecId) - .then() - .extract() - .response(); - Assertions.assertEquals(200, deleteReponse.statusCode()); - // Cool its gone. - } - }finally { - // Maybe somesort of Tomcat shutdown here? - } - } - - @Disabled("incomplete") - @Test - void retrieve_create_retrieve_delete_retrieve_xml() throws Exception{ - // Do whatever we need to do to startup the server - int port = 7000; - DataSource testDS = buildDataSource(); - String baseUri = "http://localhost:" + port; - //// Todo: there needs to be some sort of Tomcat start here. - try - { - // Read in a resource and build our test data - String resourcePath = "cwms/cda/data/dao/BEAV.Stage_Flow.BASE.PRODUCTION.xml"; - String refRatingXml = JsonRatingUtilsTest.loadResourceAsString(resourcePath); - assertNotNull(refRatingXml); - RatingSet refRatingSet = RatingXmlFactory.ratingSet(refRatingXml); - String office = "SWT"; - - String refSpecId = refRatingSet.getName(); - - String newLoc = "TESTLOC"; - - String testSpecId = refSpecId.replace("BEAV", newLoc); - String testRatingXml = refRatingXml.replace("BEAV", newLoc).replace("Beaver", "TestLoc"); - - try - { - // Make sure we can't find the new rating - Response missingReponse = given() - .baseUri(baseUri) - .accept("application/xml;version=2") - .param("office",office) - .when() - .get("/ratings/" + testSpecId) - .then() - .extract() - .response(); - Assertions.assertEquals(404, missingReponse.statusCode()); - // Cool, it's not there. - - // Now lets create it from json. - Response createReponse = given() - .baseUri(baseUri) - .body(testRatingXml) - .accept("application/xml;version=2") - .when() - .post("/ratings") - .then() - .extract() - .response(); - Assertions.assertEquals(200, createReponse.statusCode()); - // Cool, created it. - - // Now lets get it - Response secondGetReponse = given() - .baseUri(baseUri) - .accept("application/xml;version=2") - .param("office", office) - .when() - .get("/ratings/" + testSpecId) - .then() - .extract() - .response(); - Assertions.assertEquals(200, secondGetReponse.statusCode()); - // Cool, got it. - } - finally - { - // Now lets delete it - Response deleteReponse = given() - .baseUri(baseUri) - .accept("application/xml;version=2") - .param("office", office) - .when() - .delete("/ratings/" + testSpecId) - .then() - .extract() - .response(); - Assertions.assertEquals(200, deleteReponse.statusCode()); - // Cool its gone. - } - }finally { - // Maybe somesort of Tomcat shutdown here? - } - } - - - @Nullable - private DataSource buildDataSource() - { - DataSource testDS = new DataSource() - { - @Override - public Connection getConnection() throws SQLException - { - return DaoTest.getConnection(); - } - - @Override - public Connection getConnection(String username, String password) throws SQLException - { - return DaoTest.getConnection(); - } - - @Override - public T unwrap(Class iface) throws SQLException - { - return null; - } - - @Override - public boolean isWrapperFor(Class iface) throws SQLException - { - return false; - } - - @Override - public PrintWriter getLogWriter() throws SQLException - { - return null; - } - - @Override - public void setLogWriter(PrintWriter out) throws SQLException - { - - } - - @Override - public void setLoginTimeout(int seconds) throws SQLException - { - - } - - @Override - public int getLoginTimeout() throws SQLException - { - return 0; - } - - @Override - public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException - { - return null; - } - }; - return testDS; - } - - -}