diff --git a/pom.xml b/pom.xml index 612bd1e2..c38f606c 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 1.12.2 5.11.2 - 10.7.14 + 10.8.0 0.5.10 ${project.version} diff --git a/src/main/java/com/uid2/admin/vertx/service/ServiceLinkService.java b/src/main/java/com/uid2/admin/vertx/service/ServiceLinkService.java index 109f7b57..44dee73a 100644 --- a/src/main/java/com/uid2/admin/vertx/service/ServiceLinkService.java +++ b/src/main/java/com/uid2/admin/vertx/service/ServiceLinkService.java @@ -162,6 +162,7 @@ private void handleServiceLinkUpdate(RoutingContext rc) { Integer siteId = body.getInteger("site_id"); String name = body.getString("name"); JsonArray rolesJson = body.getJsonArray("roles"); + Boolean disabled = body.getBoolean("disabled"); if (siteId == null || serviceId == null || linkId == null || linkId.isEmpty()) { ResponseUtil.error(rc, 400, "required parameters: site_id, service_id, link_id"); @@ -195,6 +196,10 @@ private void handleServiceLinkUpdate(RoutingContext rc) { serviceLink.setName(name); } + if (disabled != null) { + serviceLink.setDisabled(disabled); + } + if (rolesJson != null && !rolesJson.isEmpty()) { final Set roles; try { @@ -259,6 +264,7 @@ private JsonObject toJson(ServiceLink s) { jsonObject.put("site_id", s.getSiteId()); jsonObject.put("name", s.getName()); jsonObject.put("roles", s.getRoles()); + jsonObject.put("disabled", s.isDisabled()); return jsonObject; } diff --git a/src/main/java/com/uid2/admin/vertx/service/ServiceService.java b/src/main/java/com/uid2/admin/vertx/service/ServiceService.java index 0f4f7239..2f989d09 100644 --- a/src/main/java/com/uid2/admin/vertx/service/ServiceService.java +++ b/src/main/java/com/uid2/admin/vertx/service/ServiceService.java @@ -191,6 +191,7 @@ private void handleUpdate(RoutingContext rc) { Integer siteId = body.getInteger("site_id"); String name = body.getString("name"); String linkIdRegex = body.getString("link_id_regex"); + Boolean disabled = body.getBoolean("disabled"); JsonArray rolesSpec = null; if (body.getString("roles") != null && !body.getString("roles").isEmpty()) { @@ -242,6 +243,10 @@ private void handleUpdate(RoutingContext rc) { service.setLinkIdRegex(linkIdRegex); } + if (disabled != null) { + service.setDisabled(disabled); + } + if (siteId != null && siteId != 0) { service.setSiteId(siteId); } @@ -295,6 +300,7 @@ private JsonObject toJson(Service s) { jsonObject.put("name", s.getName()); jsonObject.put("roles", s.getRoles()); jsonObject.put("link_id_regex", s.getLinkIdRegex()); + jsonObject.put("disabled", s.isDisabled()); return jsonObject; } diff --git a/src/test/java/com/uid2/admin/vertx/ServiceLinkServiceTest.java b/src/test/java/com/uid2/admin/vertx/ServiceLinkServiceTest.java index 5d8ae1ad..c35df412 100644 --- a/src/test/java/com/uid2/admin/vertx/ServiceLinkServiceTest.java +++ b/src/test/java/com/uid2/admin/vertx/ServiceLinkServiceTest.java @@ -13,6 +13,7 @@ import io.vertx.junit5.VertxTestContext; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; @@ -387,6 +388,34 @@ void updateServiceLink_updateNameOnly_succeeds(Vertx vertx, VertxTestContext tes }); } + @Test + void updateServiceLink_updateDisabledOnly_succeeds(Vertx vertx, VertxTestContext testContext) { + fakeAuth(Role.MAINTAINER); + + setSites(new Site(123, "name1", false)); + setServices(new Service(1, 123, "name1", Set.of(Role.MAINTAINER, Role.MAPPER))); + ServiceLink existingLink = new ServiceLink("link1", 1, 123, "name1", Set.of(Role.MAPPER)); + setServiceLinks(existingLink); + + JsonObject jo = new JsonObject(); + jo.put("link_id", "link1"); + jo.put("service_id", 1); + jo.put("site_id", 123); + jo.put("disabled", true); + + ServiceLink expected = new ServiceLink("link1", 1, 123, "name1", Set.of(Role.MAPPER)); + expected.setDisabled(true); + + post(vertx, testContext, "api/service_link/update", jo.encode(), response -> { + assertEquals(200, response.statusCode()); + checkServiceLinkJson(expected, response.bodyAsJsonObject()); + verify(serviceStoreWriter, never()).upload(null, null); + verify(serviceLinkStoreWriter, times(1)).upload(List.of(expected), null); + + testContext.completeNow(); + }); + } + @Test void updateServiceLink_updateRoleOnly_succeeds(Vertx vertx, VertxTestContext testContext) { fakeAuth(Role.MAINTAINER); @@ -442,6 +471,35 @@ void updateServiceLink_updateRoleAndName_succeeds(Vertx vertx, VertxTestContext }); } + @Test + void updateServiceLink_updateRoleNameDisabled_succeeds(Vertx vertx, VertxTestContext testContext) { + fakeAuth(Role.MAINTAINER); + + setSites(new Site(123, "name1", false)); + setServices(new Service(1, 123, "name1", Set.of(Role.MAINTAINER, Role.MAPPER, Role.SHARER))); + ServiceLink existingLink = new ServiceLink("link1", 1, 123, "name1", Set.of(Role.MAPPER)); + setServiceLinks(existingLink); + + JsonObject jo = new JsonObject(); + jo.put("link_id", "link1"); + jo.put("service_id", 1); + jo.put("site_id", 123); + jo.put("name", "newname"); + jo.put("roles", JsonArray.of(Role.MAPPER, Role.SHARER)); + jo.put("disabled", true); + + ServiceLink expected = new ServiceLink("link1", 1, 123, "newname", Set.of(Role.MAPPER, Role.SHARER), true); + + post(vertx, testContext, "api/service_link/update", jo.encode(), response -> { + assertEquals(200, response.statusCode()); + checkServiceLinkJson(expected, response.bodyAsJsonObject()); + verify(serviceStoreWriter, never()).upload(null, null); + verify(serviceLinkStoreWriter, times(1)).upload(List.of(expected), null); + + testContext.completeNow(); + }); + } + @Test void updateServiceLink_roleDoesNotExist_returnsError(Vertx vertx, VertxTestContext testContext) { fakeAuth(Role.MAINTAINER); @@ -622,20 +680,20 @@ void addServiceLink_linkIdRegex_validation(String linkIdRegex, String linkId, bo private static java.util.stream.Stream linkIdRegexCases() { return java.util.stream.Stream.of( - org.junit.jupiter.params.provider.Arguments.of("link[0-9]+", "invalidLink", false), - org.junit.jupiter.params.provider.Arguments.of("link[0-9]+", "link42", true), - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "XY12345", true), // snowflake valid - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "UID2_ENVIRONMENT", true), // snowflake valid - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "xy12345", false), // snowflake invalid, lowercase - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "X", true), // snowflake valid, minimum length - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "X".repeat(256), true), // snowflake valid, maximum length - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "X".repeat(257), false), // snowflake invalid, exceeds maximum length - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", " XY12345", false), // snowflake invalid, leading whitespace - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "XY12345 ", false), // snowflake invalid, trailing whitespace - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "XY 12345", false), // snowflake invalid, whitespace in the middle - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "", false), // snowflake invalid, empty - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", " ", false), // snowflake invalid, just whitespace - org.junit.jupiter.params.provider.Arguments.of("^[A-Z0-9_]{1,256}$", "XY_12345", true) // snowflake valid, used underscore + Arguments.of("link[0-9]+", "invalidLink", false), + Arguments.of("link[0-9]+", "link42", true), + Arguments.of("^[A-Z0-9_]{1,256}$", "XY12345", true), // snowflake valid + Arguments.of("^[A-Z0-9_]{1,256}$", "UID2_ENVIRONMENT", true), // snowflake valid + Arguments.of("^[A-Z0-9_]{1,256}$", "xy12345", false), // snowflake invalid, lowercase + Arguments.of("^[A-Z0-9_]{1,256}$", "X", true), // snowflake valid, minimum length + Arguments.of("^[A-Z0-9_]{1,256}$", "X".repeat(256), true), // snowflake valid, maximum length + Arguments.of("^[A-Z0-9_]{1,256}$", "X".repeat(257), false), // snowflake invalid, exceeds maximum length + Arguments.of("^[A-Z0-9_]{1,256}$", " XY12345", false), // snowflake invalid, leading whitespace + Arguments.of("^[A-Z0-9_]{1,256}$", "XY12345 ", false), // snowflake invalid, trailing whitespace + Arguments.of("^[A-Z0-9_]{1,256}$", "XY 12345", false), // snowflake invalid, whitespace in the middle + Arguments.of("^[A-Z0-9_]{1,256}$", "", false), // snowflake invalid, empty + Arguments.of("^[A-Z0-9_]{1,256}$", " ", false), // snowflake invalid, just whitespace + Arguments.of("^[A-Z0-9_]{1,256}$", "XY_12345", true) // snowflake valid, used underscore ); } } diff --git a/src/test/java/com/uid2/admin/vertx/ServiceServiceTest.java b/src/test/java/com/uid2/admin/vertx/ServiceServiceTest.java index 36f84260..713a01bc 100644 --- a/src/test/java/com/uid2/admin/vertx/ServiceServiceTest.java +++ b/src/test/java/com/uid2/admin/vertx/ServiceServiceTest.java @@ -580,6 +580,26 @@ void updateName(Vertx vertx, VertxTestContext testContext) { }); } + @Test + void updateDisabled(Vertx vertx, VertxTestContext testContext) { + fakeAuth(Role.PRIVILEGED); + + Service existingService = new Service(1, 123, "name1", Set.of(Role.MAINTAINER)); + setServices(existingService); + + JsonObject jo = new JsonObject(); + jo.put("service_id", 1); + jo.put("disabled", true); + + post(vertx, testContext, "api/service/update", jo.encode(), response -> { + assertEquals(200, response.statusCode()); + existingService.setDisabled(true); + checkServiceJson(existingService, response.bodyAsJsonObject()); + verify(serviceStoreWriter, times(1)).upload(List.of(existingService), null); + testContext.completeNow(); + }); + } + @Test void updateSiteId(Vertx vertx, VertxTestContext testContext) { fakeAuth(Role.PRIVILEGED); @@ -600,6 +620,32 @@ void updateSiteId(Vertx vertx, VertxTestContext testContext) { }); } + @Test + void updateNameDisabledSiteIdRoles(Vertx vertx, VertxTestContext testContext) { + fakeAuth(Role.PRIVILEGED); + + Service existingService = new Service(1, 123, "name1", Set.of(Role.MAINTAINER)); + setServices(existingService); + + JsonObject jo = new JsonObject(); + jo.put("service_id", 1); + jo.put("site_id", 456); + jo.put("name", "newname"); + jo.put("disabled", true); + jo.put("roles", JsonArray.of(Role.MAINTAINER, Role.MAPPER, Role.SHARER)); + + post(vertx, testContext, "api/service/update", jo.encode(), response -> { + assertEquals(200, response.statusCode()); + existingService.setSiteId(456); + existingService.setName("newname"); + existingService.setDisabled(true); + existingService.setRoles(Set.of(Role.MAINTAINER, Role.MAPPER, Role.SHARER)); + checkServiceJson(existingService, response.bodyAsJsonObject()); + verify(serviceStoreWriter, times(1)).upload(List.of(existingService), null); + testContext.completeNow(); + }); + } + @Test void updateWithEmptyValues(Vertx vertx, VertxTestContext testContext) { fakeAuth(Role.PRIVILEGED); diff --git a/webroot/adm/service-link.html b/webroot/adm/service-link.html index eacd4379..7e4e6c36 100644 --- a/webroot/adm/service-link.html +++ b/webroot/adm/service-link.html @@ -75,6 +75,12 @@

UID2 Env - Service Link Management

] }; + const disabledInput = { + name: 'disabled', + label: 'Disable Service Link', + type: 'checkbox' + }; + const operationConfig = { read: [ { @@ -141,7 +147,8 @@

UID2 Env - Service Link Management

serviceIdInput, siteIdInput, linkNameInput, - rolesInput + rolesInput, + disabledInput, ], apiCall: { method: 'POST', @@ -156,7 +163,8 @@

UID2 Env - Service Link Management

service_id: parseInt(inputs.serviceId), site_id: parseInt(inputs.siteId), name: inputs.linkName || '', - roles: rolesArray + roles: rolesArray, + disabled: typeof inputs.disabled === 'boolean' ? inputs.disabled : undefined, }; } } diff --git a/webroot/adm/service.html b/webroot/adm/service.html index e549403e..cf3bbc83 100644 --- a/webroot/adm/service.html +++ b/webroot/adm/service.html @@ -74,6 +74,12 @@

UID2 Env - Service Management

label: 'Link Id Regex' }; + const disabledInput = { + name: 'disabled', + label: 'Disable Service', + type: 'checkbox' + }; + const operationConfig = { read: [ { @@ -142,7 +148,8 @@

UID2 Env - Service Management

{...serviceNameInput, required: false}, {...siteIdInput, required: false}, {...rolesInput, required: false}, - linkIdRegexInput + linkIdRegexInput, + disabledInput ], apiCall: { method: 'POST', @@ -157,6 +164,7 @@

UID2 Env - Service Management

if (rolesArray.length > 0) payload.roles = rolesArray; if (inputs.linkIdRegex) payload.link_id_regex = inputs.linkIdRegex; + if (typeof inputs.disabled === 'boolean') payload.disabled = inputs.disabled; return payload; } }