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 f2e6129c6..3cf9cf07f 100644 --- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java @@ -127,6 +127,7 @@ import cwms.cda.api.watersupply.WaterContractDeleteController; import cwms.cda.api.watersupply.WaterContractTypeCatalogController; import cwms.cda.api.watersupply.WaterContractTypeCreateController; +import cwms.cda.api.watersupply.WaterContractTypeDeleteController; import cwms.cda.api.watersupply.WaterContractUpdateController; import cwms.cda.api.watersupply.WaterPumpDisassociateController; import cwms.cda.api.watersupply.WaterUserCatalogController; @@ -627,6 +628,7 @@ private void addWaterContractHandlers(String path, RouteRole[] requiredRoles) { private void addWaterContractTypeHandlers(String path, RouteRole[] requiredRoles) { post(path, new WaterContractTypeCreateController(metrics), requiredRoles); get(path, new WaterContractTypeCatalogController(metrics)); + delete(path + "/{display-value}", new WaterContractTypeDeleteController(metrics), requiredRoles); } /** diff --git a/cwms-data-api/src/main/java/cwms/cda/api/watersupply/WaterContractTypeCreateController.java b/cwms-data-api/src/main/java/cwms/cda/api/watersupply/WaterContractTypeCreateController.java index 3b79c3696..a01f6bad9 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/watersupply/WaterContractTypeCreateController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/watersupply/WaterContractTypeCreateController.java @@ -36,7 +36,6 @@ import cwms.cda.data.dto.LookupType; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; -import io.javalin.core.util.Header; import io.javalin.http.Context; import io.javalin.http.Handler; import io.javalin.plugin.openapi.annotations.HttpMethod; @@ -86,7 +85,7 @@ public void handle(@NotNull Context ctx) { ctx.contentType(contentType.toString()); LookupType contractType = Formats.parseContent(contentType, ctx.body(), LookupType.class); WaterContractDao contractDao = getContractDao(dsl); - contractDao.storeWaterContractTypes(contractType, failIfExists); + contractDao.storeWaterContractType(contractType, failIfExists); ctx.status(HttpServletResponse.SC_CREATED).json("Contract type successfully stored to CWMS."); } } diff --git a/cwms-data-api/src/main/java/cwms/cda/api/watersupply/WaterContractTypeDeleteController.java b/cwms-data-api/src/main/java/cwms/cda/api/watersupply/WaterContractTypeDeleteController.java new file mode 100644 index 000000000..fc25b4b2c --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/watersupply/WaterContractTypeDeleteController.java @@ -0,0 +1,84 @@ +/* + * + * 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.watersupply; + +import static cwms.cda.api.Controllers.DELETE; +import static cwms.cda.api.Controllers.OFFICE; +import static cwms.cda.data.dao.JooqDao.getDslContext; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import cwms.cda.data.dao.watersupply.WaterContractDao; +import cwms.cda.data.dto.LookupType; +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.OpenApiParam; +import javax.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; + + +public final class WaterContractTypeDeleteController extends WaterSupplyControllerBase implements Handler { + + private static final String DISPLAY_VALUE = "display-value"; + + public WaterContractTypeDeleteController(MetricRegistry metrics) { + waterMetrics(metrics); + } + + @OpenApi( + pathParams = { + @OpenApiParam(name = OFFICE, required = true, description = "The office associated with " + + "the contract type to delete"), + @OpenApiParam(name = DISPLAY_VALUE, required = true, description = "The location associated with " + + "the contract type to delete"), + }, + description = "Delete a water contract type", + method = HttpMethod.DELETE, + path = "/projects/{office}/contract-types/{display-value}", + tags = {TAG} + ) + + @Override + public void handle(@NotNull Context ctx) { + try (Timer.Context ignored = markAndTime(DELETE)) { + DSLContext dsl = getDslContext(ctx); + String office = ctx.pathParam(OFFICE); + String displayValue = ctx.pathParam(DISPLAY_VALUE); + String formatHeader = ctx.req.getContentType(); + ContentType contentType = Formats.parseHeader(formatHeader, LookupType.class); + ctx.contentType(contentType.toString()); + WaterContractDao dao = new WaterContractDao(dsl); + dao.deleteWaterContractType(office, displayValue); + ctx.status(HttpServletResponse.SC_NO_CONTENT).json("Contract type successfully deleted from CWMS."); + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/watersupply/WaterContractDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/watersupply/WaterContractDao.java index 742025967..7fd17c287 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/watersupply/WaterContractDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/watersupply/WaterContractDao.java @@ -30,6 +30,7 @@ import cwms.cda.api.errors.NotFoundException; import cwms.cda.data.dao.JooqDao; +import cwms.cda.data.dao.LookupTypeDao; import cwms.cda.data.dao.location.kind.LocationUtil; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.LookupType; @@ -176,7 +177,17 @@ public void deleteWaterContract(WaterUserContract contract, DeleteMethod deleteA }); } - public void storeWaterContractTypes(LookupType lookupType, + public void deleteWaterContractType(String office, String displayValue) { + connection(dsl, c -> { + setOffice(c, office); + LookupTypeDao lookupTypeDao = new LookupTypeDao(DSL.using(c)); + String category = "AT_WS_CONTRACT_TYPE"; + String prefix = "WS_CONTRACT_TYPE"; + lookupTypeDao.deleteLookupType(category, prefix, office, displayValue); + }); + } + + public void storeWaterContractType(LookupType lookupType, boolean failIfExists) { connection(dsl, c -> { setOffice(c, lookupType.getOfficeId()); diff --git a/cwms-data-api/src/test/java/cwms/cda/api/WaterContractTypeCreateControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/WaterContractTypeControllerTestIT.java similarity index 51% rename from cwms-data-api/src/test/java/cwms/cda/api/WaterContractTypeCreateControllerTestIT.java rename to cwms-data-api/src/test/java/cwms/cda/api/WaterContractTypeControllerTestIT.java index cccb2b580..19fd4109b 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/WaterContractTypeCreateControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/WaterContractTypeControllerTestIT.java @@ -29,39 +29,53 @@ import static cwms.cda.data.dao.DaoTest.getDslContext; import static cwms.cda.security.KeyAccessManager.AUTH_HEADER; import static io.restassured.RestAssured.given; +import static java.lang.String.format; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import cwms.cda.api.errors.NotFoundException; import cwms.cda.data.dao.LookupTypeDao; import cwms.cda.data.dto.LookupType; import cwms.cda.formatters.Formats; import cwms.cda.formatters.json.JsonV1; +import cwms.cda.helpers.DTOMatch; import fixtures.CwmsDataApiSetupCallback; import fixtures.TestAccounts; import io.restassured.filter.log.LogDetail; import mil.army.usace.hec.test.database.CwmsDatabaseContainer; import org.jooq.DSLContext; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import javax.servlet.http.HttpServletResponse; import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; @Tag("integration") -class WaterContractTypeCreateControllerTestIT extends DataApiTestIT { +class WaterContractTypeControllerTestIT extends DataApiTestIT { private static final String OFFICE_ID = "SWT"; private static final LookupType CONTRACT_TYPE; + public static final Logger LOGGER = + Logger.getLogger(WaterContractTypeControllerTestIT.class.getName()); static { CONTRACT_TYPE = new LookupType.Builder().withActive(true).withOfficeId(OFFICE_ID) .withDisplayValue("TEST Contract Type").withTooltip("TEST LOOKUP").build(); } + @AfterEach + void cleanup() throws SQLException { + cleanupType(); + } + @Test void test_create_get_WaterContractType() throws Exception { // Test Structure // 1) Create a WaterContractType // 2) Get the WaterContractType, assert it exists - // 3) Cleanup TestAccounts.KeyUser user = TestAccounts.KeyUser.SWT_NORMAL; String json = JsonV1.buildObjectMapper().writeValueAsString(CONTRACT_TYPE); @@ -101,9 +115,87 @@ void test_create_get_WaterContractType() throws Exception { .body("[0].tooltip", equalTo(CONTRACT_TYPE.getTooltip())) .body("[0].active", equalTo(CONTRACT_TYPE.getActive())) ; + } - // cleanup - cleanupType(); + @Test + void test_store_delete_WaterContractType() throws Exception { + // Test Structure + // 1) Create a WaterContractType + // 2) Get the WaterContractType, assert it exists + // 3) Delete the WaterContractType + // 4) Get the WaterContractType, assert it does not exist + + TestAccounts.KeyUser user = TestAccounts.KeyUser.SWT_NORMAL; + String json = JsonV1.buildObjectMapper().writeValueAsString(CONTRACT_TYPE); + + // create water contract type + given() + .log().ifValidationFails(LogDetail.ALL, true) + .contentType(Formats.JSONV1) + .body(json) + .queryParam("fail-if-exists", false) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/projects/" + OFFICE_ID + "/contract-types") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)) + ; + + // get water contract type and assert that it exists + given() + .log().ifValidationFails(LogDetail.ALL, true) + .contentType(Formats.JSONV1) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/projects/" + OFFICE_ID + "/contract-types") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("[0].office-id", equalTo(OFFICE_ID)) + .body("[0].display-value", equalTo(CONTRACT_TYPE.getDisplayValue())) + .body("[0].tooltip", equalTo(CONTRACT_TYPE.getTooltip())) + .body("[0].active", equalTo(CONTRACT_TYPE.getActive())) + ; + + // delete water contract type + given() + .log().ifValidationFails(LogDetail.ALL, true) + .contentType(Formats.JSONV1) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("/projects/" + OFFICE_ID + "/contract-types/" + CONTRACT_TYPE.getDisplayValue()) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)) + ; + + // get water contract type and assert that it does not exist + List results = Arrays.asList(given() + .log().ifValidationFails(LogDetail.ALL, true) + .contentType(Formats.JSONV1) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/projects/" + OFFICE_ID + "/contract-types") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract().body().as(LookupType[].class)) + ; + DTOMatch.assertDoesNotContainDto(results, CONTRACT_TYPE, + (i, s) -> i.getDisplayValue().equalsIgnoreCase(s.getDisplayValue()), "Contract Type not deleted"); } private void cleanupType() throws SQLException { @@ -111,8 +203,13 @@ private void cleanupType() throws SQLException { databaseLink.connection(c -> { DSLContext ctx = getDslContext(c, OFFICE_ID); LookupTypeDao lookupTypeDao = new LookupTypeDao(ctx); - lookupTypeDao.deleteLookupType("AT_WS_CONTRACT_TYPE", "WS_CONTRACT_TYPE", - CONTRACT_TYPE.getOfficeId(), CONTRACT_TYPE.getDisplayValue()); + try { + + lookupTypeDao.deleteLookupType("AT_WS_CONTRACT_TYPE", "WS_CONTRACT_TYPE", + CONTRACT_TYPE.getOfficeId(), CONTRACT_TYPE.getDisplayValue()); + } catch (NotFoundException e) { + LOGGER.log(Level.CONFIG, format("Cleanup failed to delete lookup type: %s", e.getMessage())); + } }, CwmsDataApiSetupCallback.getWebUser()); } } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/WaterPumpDisassociateControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/WaterPumpDisassociateControllerTestIT.java index c8ea938a5..bc5c03f3a 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/WaterPumpDisassociateControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/WaterPumpDisassociateControllerTestIT.java @@ -155,7 +155,7 @@ static void setUp() throws Exception { projectDao.store(project1, true); projectDao.store(project2, true); waterContractDao.storeWaterUser(CONTRACT.getWaterUser(), false); - waterContractDao.storeWaterContractTypes(CONTRACT.getContractType(), false); + waterContractDao.storeWaterContractType(CONTRACT.getContractType(), false); waterContractDao.storeWaterUser(CONTRACT_NO_PUMP.getWaterUser(), false); } catch (IOException e) { throw new RuntimeException(e); diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/watersupply/WaterContractDaoTestIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/watersupply/WaterContractDaoTestIT.java index e43bf96c1..dcc0c1335 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/watersupply/WaterContractDaoTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/watersupply/WaterContractDaoTestIT.java @@ -135,6 +135,28 @@ void cleanup() throws SQLException { }, CwmsDataApiSetupCallback.getWebUser()); } + @Test + void testStoreAndDeleteWaterContractType() throws Exception { + LookupType contractType = new LookupType.Builder() + .withTooltip("Test Tooltip Delete") + .withActive(true) + .withDisplayValue("Test Display Value Delete") + .withOfficeId(OFFICE_ID).build(); + CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); + db.connection(c -> { + DSLContext ctx = getDslContext(c, OFFICE_ID); + WaterContractDao dao = new WaterContractDao(ctx); + dao.storeWaterContractType(contractType, false); + List retrievedType = dao.getAllWaterContractTypes(OFFICE_ID); + DTOMatch.assertContainsDto(retrievedType, contractType, WaterContractDaoTestIT::typeMatches, + DTOMatch::assertMatch, "Contract Type not stored"); + dao.deleteWaterContractType(contractType.getOfficeId(), contractType.getDisplayValue()); + retrievedType = dao.getAllWaterContractTypes(OFFICE_ID); + DTOMatch.assertDoesNotContainDto(retrievedType, contractType, WaterContractDaoTestIT::typeMatches, + "Contract Type not deleted"); + }, CwmsDataApiSetupCallback.getWebUser()); + } + @Test void testStoreAndRetrieveWaterUserList() throws Exception { CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); @@ -243,7 +265,7 @@ void testStoreAndRetrieveWaterContractType() throws Exception { .withActive(true) .withDisplayValue("Test Display Value") .withOfficeId(OFFICE_ID).build(); - dao.storeWaterContractTypes(contractType, false); + dao.storeWaterContractType(contractType, false); List results = dao.getAllWaterContractTypes(OFFICE_ID); DTOMatch.assertMatch(contractType, results.get(0)); }, CwmsDataApiSetupCallback.getWebUser()); @@ -356,6 +378,13 @@ void testRemovePumpFromContract() throws Exception { }, CwmsDataApiSetupCallback.getWebUser()); } + private static boolean typeMatches(LookupType type, LookupType other) { + return type.getTooltip().equals(other.getTooltip()) && + type.getDisplayValue().equals(other.getDisplayValue()) && + type.getOfficeId().equals(other.getOfficeId()) && + type.getActive() == other.getActive(); + } + private static WaterUser buildTestWaterUser(String entityName) { return new WaterUser.Builder().withEntityName(entityName).withProjectId(new CwmsId.Builder() .withName(testLocation.getName())