From ca30259a6bfb60a9007ce1c5f0ad978c526d5434 Mon Sep 17 00:00:00 2001 From: Markus Stetschnig Date: Thu, 23 Apr 2026 14:53:39 +0200 Subject: [PATCH 1/2] feat: replace custom webhook events with consistant ping --- .../negotiator/webhook/WebhookController.java | 20 +--- .../webhook/WebhookDeliveryDispatcher.java | 7 +- .../webhook/WebhookEventListener.java | 3 +- .../negotiator/webhook/WebhookService.java | 24 ++-- .../webhook/WebhookServiceImpl.java | 45 ++++---- .../webhook/event/PingWebhookEvent.java | 8 ++ .../webhook/event/WebhookEventType.java | 6 +- .../webhook/event/WebhookPayloadEnvelope.java | 7 +- .../api/WebhookControllerTest.java | 103 ++++++------------ .../WebhookDeliverySslIntegrationTest.java | 35 ++---- ...eryTransactionBoundaryIntegrationTest.java | 11 +- ...DeliveryTransportFaultIntegrationTest.java | 16 +-- .../WebhookRedeliveryIntegrationTest.java | 10 +- ...eReferenceVerificationIntegrationTest.java | 4 +- .../unit/model/WebhookEntityTest.java | 16 +-- .../WebhookDeliveryDispatcherTest.java | 7 +- .../webhook/WebhookDeliveryPersisterTest.java | 4 +- .../webhook/WebhookEventListenerTest.java | 12 +- frontend/src/store/admin.js | 12 +- 19 files changed, 126 insertions(+), 224 deletions(-) create mode 100644 backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/PingWebhookEvent.java diff --git a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookController.java b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookController.java index 2bda31259..15ba7291d 100644 --- a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookController.java +++ b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookController.java @@ -1,8 +1,6 @@ package eu.bbmri_eric.negotiator.webhook; -import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -76,19 +74,11 @@ public void deleteWebhook(@PathVariable Long id) { } @Operation( - summary = "Add a delivery to a webhook", - description = "Adds a new delivery to the specified webhook and returns the response") - @PostMapping(value = "/{id}/deliveries") - public EntityModel addDelivery( - @PathVariable Long id, - @Valid - @RequestBody - @Schema( - description = "Content to deliver", - example = "{\"test\":\"yes\"}", - type = "object") - String content) { - DeliveryDTO dto = webhookService.deliver(content, WebhookEventType.CUSTOM, id); + summary = "Send a ping delivery to a webhook", + description = "Sends a predefined ping payload to the specified webhook") + @PostMapping(value = "/{id}/ping") + public EntityModel ping(@PathVariable Long id) { + DeliveryDTO dto = webhookService.ping(id); return EntityModel.of(dto); } diff --git a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryDispatcher.java b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryDispatcher.java index e2b258ee3..4a87ddbd4 100644 --- a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryDispatcher.java +++ b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryDispatcher.java @@ -1,7 +1,6 @@ package eu.bbmri_eric.negotiator.webhook; import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType; -import java.time.Instant; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -23,11 +22,9 @@ class WebhookDeliveryDispatcher { * @param webhookId the id of the target webhook * @param payload the JSON payload to deliver * @param eventType the event type header for the delivery - * @param occurredAt the event timestamp to include as a header */ @Async("webhookDeliveryExecutor") - void scheduleDelivery( - Long webhookId, String payload, WebhookEventType eventType, Instant occurredAt) { - webhookService.deliver(payload, eventType, webhookId, occurredAt); + void scheduleDelivery(Long webhookId, String payload, WebhookEventType eventType) { + webhookService.deliver(payload, eventType, webhookId); } } diff --git a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookEventListener.java b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookEventListener.java index 6641a5213..6ffae549a 100644 --- a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookEventListener.java +++ b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookEventListener.java @@ -55,8 +55,7 @@ private void dispatch(WebhookPayloadEnvelope payloadEnvelope) { } List webhookIds = webhookService.getActiveWebhookIds(); for (Long webhookId : webhookIds) { - webhookDeliveryDispatcher.scheduleDelivery( - webhookId, payload, payloadEnvelope.type(), payloadEnvelope.timestamp()); + webhookDeliveryDispatcher.scheduleDelivery(webhookId, payload, payloadEnvelope.type()); } } diff --git a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookService.java b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookService.java index 590c60881..ece92f45b 100644 --- a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookService.java +++ b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookService.java @@ -1,7 +1,6 @@ package eu.bbmri_eric.negotiator.webhook; import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType; -import java.time.Instant; import java.util.List; public interface WebhookService { @@ -45,8 +44,7 @@ public interface WebhookService { void deleteWebhook(Long id); /** - * Creates a new delivery for a given webhook, using the current time as the occurred-at - * timestamp. + * Creates a new delivery for a given webhook. * * @param jsonPayload the JSON content for the delivery * @param eventType the event type header for the delivery @@ -55,6 +53,14 @@ public interface WebhookService { */ DeliveryDTO deliver(String jsonPayload, WebhookEventType eventType, Long webhookId); + /** + * Sends a standardized ping delivery for the given webhook. + * + * @param webhookId the id of the target webhook + * @return a DTO representing the newly created ping delivery + */ + DeliveryDTO ping(Long webhookId); + /** * Creates a manual redelivery for a previously recorded delivery. * @@ -64,18 +70,6 @@ public interface WebhookService { */ DeliveryDTO redeliver(Long webhookId, String deliveryId); - /** - * Creates a new delivery for a given webhook with an explicit occurred-at timestamp. - * - * @param jsonPayload the JSON content for the delivery - * @param eventType the event type header for the delivery - * @param webhookId the id of the target webhook - * @param occurredAt the event timestamp to include as a header - * @return a DTO representing the newly created delivery - */ - DeliveryDTO deliver( - String jsonPayload, WebhookEventType eventType, Long webhookId, Instant occurredAt); - /** * Returns the ids of all currently active webhooks. * diff --git a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookServiceImpl.java b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookServiceImpl.java index 65ca5c5c7..6b7d65daf 100644 --- a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookServiceImpl.java +++ b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/WebhookServiceImpl.java @@ -1,7 +1,6 @@ package eu.bbmri_eric.negotiator.webhook; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import eu.bbmri_eric.negotiator.common.JSONUtils; import eu.bbmri_eric.negotiator.common.exceptions.EntityNotFoundException; @@ -113,7 +112,22 @@ public void deleteWebhook(Long id) { @Override public DeliveryDTO deliver(String jsonPayload, WebhookEventType eventType, Long webhookId) { - return deliver(jsonPayload, eventType, webhookId, Instant.now()); + if (!JSONUtils.isJSONValid(jsonPayload)) { + throw new IllegalArgumentException("Content is not a valid JSON"); + } + Webhook webhook = getWebhook(webhookId); + RestTemplate restTemplate = + webhook.isSslVerification() ? secureRestTemplate : insecureRestTemplate; + String webhookMessageId = UUID.randomUUID().toString(); + return deliverWebhook(jsonPayload, restTemplate, webhook, eventType, null, webhookMessageId); + } + + @Override + public DeliveryDTO ping(Long webhookId) { + Instant occurredAt = Instant.now(); + var payloadEnvelope = WebhookPayloadEnvelope.ping(webhookId, occurredAt); + String payload = serializePayload(payloadEnvelope); + return deliver(payload, WebhookEventType.PING, webhookId); } @Override @@ -142,21 +156,6 @@ public DeliveryDTO redeliver(Long webhookId, String deliveryId) { rootDeliveryId); } - @Override - public DeliveryDTO deliver( - String jsonPayload, WebhookEventType eventType, Long webhookId, Instant occurredAt) { - String payloadToDeliver = toPayloadEnvelopeIfNeeded(jsonPayload, eventType, occurredAt); - if (!JSONUtils.isJSONValid(payloadToDeliver)) { - throw new IllegalArgumentException("Content is not a valid JSON"); - } - Webhook webhook = getWebhook(webhookId); - RestTemplate restTemplate = - webhook.isSslVerification() ? secureRestTemplate : insecureRestTemplate; - String webhookMessageId = UUID.randomUUID().toString(); - return deliverWebhook( - payloadToDeliver, restTemplate, webhook, eventType, null, webhookMessageId); - } - @Override @Transactional(readOnly = true) public List getActiveWebhookIds() { @@ -271,17 +270,11 @@ private static int postWebhook( return new HttpEntity<>(jsonPayload, headers); } - private String toPayloadEnvelopeIfNeeded( - String jsonPayload, @NonNull WebhookEventType eventType, @NonNull Instant occurredAt) { - if (eventType != WebhookEventType.CUSTOM) { - return jsonPayload; - } - + private String serializePayload(Object payloadObject) { try { - JsonNode customData = objectMapper.readTree(jsonPayload); - return objectMapper.writeValueAsString(WebhookPayloadEnvelope.custom(occurredAt, customData)); + return objectMapper.writeValueAsString(payloadObject); } catch (JsonProcessingException ex) { - throw new IllegalArgumentException("Content is not a valid JSON", ex); + throw new IllegalStateException("Could not serialize webhook payload", ex); } } diff --git a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/PingWebhookEvent.java b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/PingWebhookEvent.java new file mode 100644 index 000000000..a1bf1fd47 --- /dev/null +++ b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/PingWebhookEvent.java @@ -0,0 +1,8 @@ +package eu.bbmri_eric.negotiator.webhook.event; + +/** + * Webhook data payload for a ping delivery. + * + * @param webhookId identifier of the target webhook + */ +record PingWebhookEvent(Long webhookId) {} diff --git a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookEventType.java b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookEventType.java index 2c6f60ad4..f64facc4c 100644 --- a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookEventType.java +++ b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookEventType.java @@ -7,13 +7,13 @@ /** Defines external webhook event type names. */ @AllArgsConstructor(access = AccessLevel.PRIVATE) public enum WebhookEventType { - CUSTOM("custom"), NEGOTIATION_ADDED("negotiation.added"), - NEGOTIATION_STATE_UPDATED("negotiation.state.updated"), NEGOTIATION_INFO_UPDATED("negotiation.info.updated"), + NEGOTIATION_POST_ADDED("negotiation.post.added"), NEGOTIATION_RESOURCE_ADDED("negotiation.resource.added"), NEGOTIATION_RESOURCE_STATE_UPDATED("negotiation.resource.state.updated"), - NEGOTIATION_POST_ADDED("negotiation.post.added"); + NEGOTIATION_STATE_UPDATED("negotiation.state.updated"), + PING("ping"); private final String value; diff --git a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookPayloadEnvelope.java b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookPayloadEnvelope.java index 361d320a3..f9be3b6d9 100644 --- a/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookPayloadEnvelope.java +++ b/backend/src/main/java/eu/bbmri_eric/negotiator/webhook/event/WebhookPayloadEnvelope.java @@ -11,8 +11,9 @@ */ public record WebhookPayloadEnvelope(WebhookEventType type, Instant timestamp, T data) { - /** Creates a custom outbound payload envelope. */ - public static WebhookPayloadEnvelope custom(Instant occurredAt, T data) { - return new WebhookPayloadEnvelope<>(WebhookEventType.CUSTOM, occurredAt, data); + /** Creates a ping outbound payload envelope. */ + public static WebhookPayloadEnvelope ping(Long webhookId, Instant occurredAt) { + return new WebhookPayloadEnvelope<>( + WebhookEventType.PING, occurredAt, new PingWebhookEvent(webhookId)); } } diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/api/WebhookControllerTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/api/WebhookControllerTest.java index ae0859d09..5987a05a8 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/api/WebhookControllerTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/api/WebhookControllerTest.java @@ -198,19 +198,16 @@ void getWebhookById_unauthorized() throws Exception { @Test @WithMockUser(roles = "ADMIN") - void testDeliverSuccess(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + void testPingSuccess(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { String url = wmRuntimeInfo.getHttpBaseUrl() + "/test-endpoint"; Webhook webhook = new Webhook(url, true, true); webhook = webhookRepository.save(webhook); stubFor( post(urlEqualTo("/test-endpoint")).willReturn(aResponse().withStatus(200).withBody("OK"))); - String payload = objectMapper.writeValueAsString("{\"data\":\"fail\"}"); mockMvc .perform( - MockMvcRequestBuilders.post( - String.format("/v3/webhooks/%d/deliveries", webhook.getId())) - .contentType(MediaType.APPLICATION_JSON) - .content(payload)) + MockMvcRequestBuilders.post(String.format("/v3/webhooks/%d/ping", webhook.getId())) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(print()) .andExpect(jsonPath("$.httpStatusCode", is(200))) @@ -219,46 +216,35 @@ void testDeliverSuccess(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { @Test @WithMockUser(roles = "ADMIN") - void testDeliverInvalidJson(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + void testDeliverEndpointRemoved_returnsClientError(WireMockRuntimeInfo wmRuntimeInfo) + throws Exception { String url = wmRuntimeInfo.getHttpBaseUrl() + "/test-endpoint"; - Webhook webhook = new Webhook(url, true, true); - webhook = webhookRepository.save(webhook); - String invalidPayload = "Not a JSON"; - mockMvc - .perform( - MockMvcRequestBuilders.post( - String.format("/v3/webhooks/%d/deliveries", webhook.getId())) - .contentType(MediaType.APPLICATION_JSON) - .content(invalidPayload)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.detail", is("Content is not a valid JSON"))); - invalidPayload = "{\"data\"\"fail\"}"; + Webhook webhook = webhookRepository.save(new Webhook(url, true, true)); + String payload = "{\"data\":\"fail\"}"; + mockMvc .perform( MockMvcRequestBuilders.post( String.format("/v3/webhooks/%d/deliveries", webhook.getId())) .contentType(MediaType.APPLICATION_JSON) - .content(invalidPayload)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.detail", is("Content is not a valid JSON"))); + .content(payload)) + .andExpect(status().is4xxClientError()); } @Test @WithMockUser(roles = "ADMIN") - void testDeliverExternalServerError(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + void testPingExternalServerError(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { String url = wmRuntimeInfo.getHttpBaseUrl() + "/test-endpoint"; Webhook webhook = new Webhook(url, true, true); webhook = webhookRepository.save(webhook); stubFor( post(urlEqualTo("/test-endpoint")) .willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); - String payload = objectMapper.writeValueAsString("{\"data\":\"fail\"}"); + mockMvc .perform( - MockMvcRequestBuilders.post( - String.format("/v3/webhooks/%d/deliveries", webhook.getId())) - .contentType(MediaType.APPLICATION_JSON) - .content(payload)) + MockMvcRequestBuilders.post(String.format("/v3/webhooks/%d/ping", webhook.getId())) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(print()) .andExpect(jsonPath("$.httpStatusCode", is(500))) @@ -267,20 +253,18 @@ void testDeliverExternalServerError(WireMockRuntimeInfo wmRuntimeInfo) throws Ex @Test @WithMockUser(roles = "ADMIN") - void testDeliver_notActive_returns400(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + void testPing_notActive_returns400(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { String url = wmRuntimeInfo.getHttpBaseUrl() + "/test-endpoint"; Webhook webhook = new Webhook(url, true, false); webhook = webhookRepository.save(webhook); stubFor( post(urlEqualTo("/test-endpoint")) .willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); - String payload = objectMapper.writeValueAsString("{\"data\":\"fail\"}"); + mockMvc .perform( - MockMvcRequestBuilders.post( - String.format("/v3/webhooks/%d/deliveries", webhook.getId())) - .contentType(MediaType.APPLICATION_JSON) - .content(payload)) + MockMvcRequestBuilders.post(String.format("/v3/webhooks/%d/ping", webhook.getId())) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) .andExpect( jsonPath( @@ -289,7 +273,7 @@ void testDeliver_notActive_returns400(WireMockRuntimeInfo wmRuntimeInfo) throws @Test @WithMockUser(roles = "ADMIN") - void testDeliver_active_receivesWebhook(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + void testPing_active_receivesWebhook(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { // Build the URL for the WireMock stub endpoint. String url = wmRuntimeInfo.getHttpBaseUrl() + "/test-endpoint"; @@ -306,25 +290,21 @@ void testDeliver_active_receivesWebhook(WireMockRuntimeInfo wmRuntimeInfo) throw .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - // Prepare a JSON payload to send. - String payload = "{\"data\":\"success\"}"; - - // Perform the test delivery request via mockMvc. + // Perform the test ping request via mockMvc. mockMvc .perform( - MockMvcRequestBuilders.post( - String.format("/v3/webhooks/%d/deliveries", webhook.getId())) - .contentType(MediaType.APPLICATION_JSON) - .content(payload)) + MockMvcRequestBuilders.post(String.format("/v3/webhooks/%d/ping", webhook.getId())) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); // Verify that the stub endpoint received a POST request with the expected JSON payload. verify( postRequestedFor(urlEqualTo("/test-endpoint")) .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(matchingJsonPath("$.type", equalTo("custom"))) + .withRequestBody(matchingJsonPath("$.type", equalTo("ping"))) .withRequestBody(matchingJsonPath("$.timestamp")) - .withRequestBody(matchingJsonPath("$.data.data", equalTo("success")))); + .withRequestBody( + matchingJsonPath("$.data.webhookId", equalTo(String.valueOf(webhook.getId()))))); var requests = findAll(postRequestedFor(urlEqualTo("/test-endpoint"))); assertEquals(1, requests.size()); @@ -348,15 +328,11 @@ void redeliver_validDelivery_returnsCreatedAndLinksToOriginal(WireMockRuntimeInf .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - String payload = "{\"data\":\"redelivery\"}"; - MvcResult firstDeliveryResult = mockMvc .perform( - MockMvcRequestBuilders.post( - String.format("/v3/webhooks/%d/deliveries", webhook.getId())) - .contentType(MediaType.APPLICATION_JSON) - .content(payload)) + MockMvcRequestBuilders.post(String.format("/v3/webhooks/%d/ping", webhook.getId())) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); @@ -384,9 +360,8 @@ void redeliver_validDelivery_returnsCreatedAndLinksToOriginal(WireMockRuntimeInf 2, postRequestedFor(urlEqualTo("/test-endpoint")) .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(matchingJsonPath("$.type", equalTo("custom"))) - .withRequestBody(matchingJsonPath("$.timestamp")) - .withRequestBody(matchingJsonPath("$.data.data", equalTo("redelivery")))); + .withRequestBody(matchingJsonPath("$.type", equalTo("ping"))) + .withRequestBody(matchingJsonPath("$.timestamp"))); } @Test @@ -414,14 +389,11 @@ void redeliver_inactiveWebhook_returnsBadRequest(WireMockRuntimeInfo wmRuntimeIn stubFor( post(urlEqualTo("/test-endpoint")).willReturn(aResponse().withStatus(200).withBody("OK"))); - String payload = "{\"data\":\"redelivery\"}"; MvcResult firstDeliveryResult = mockMvc .perform( - MockMvcRequestBuilders.post( - String.format("/v3/webhooks/%d/deliveries", webhook.getId())) - .contentType(MediaType.APPLICATION_JSON) - .content(payload)) + MockMvcRequestBuilders.post(String.format("/v3/webhooks/%d/ping", webhook.getId())) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String sourceDeliveryId = @@ -460,15 +432,11 @@ void redeliver_redeliveryOfRedelivery_preservesOriginalRootId(WireMockRuntimeInf .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - String payload = "{\"data\":\"redelivery\"}"; - MvcResult sourceDeliveryResult = mockMvc .perform( - MockMvcRequestBuilders.post( - String.format("/v3/webhooks/%d/deliveries", webhook.getId())) - .contentType(MediaType.APPLICATION_JSON) - .content(payload)) + MockMvcRequestBuilders.post(String.format("/v3/webhooks/%d/ping", webhook.getId())) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); @@ -515,9 +483,8 @@ void redeliver_redeliveryOfRedelivery_preservesOriginalRootId(WireMockRuntimeInf 3, postRequestedFor(urlEqualTo("/test-endpoint")) .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(matchingJsonPath("$.type", equalTo("custom"))) - .withRequestBody(matchingJsonPath("$.timestamp")) - .withRequestBody(matchingJsonPath("$.data.data", equalTo("redelivery")))); + .withRequestBody(matchingJsonPath("$.type", equalTo("ping"))) + .withRequestBody(matchingJsonPath("$.timestamp"))); } @Test diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliverySslIntegrationTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliverySslIntegrationTest.java index 01d423fb5..fa6fe76c5 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliverySslIntegrationTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliverySslIntegrationTest.java @@ -70,18 +70,14 @@ void deliver_sslVerificationEnabled_withTrustedCert_sendsRequest() { .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - String payload = "{\"data\":\"success\"}"; - - DeliveryDTO delivery = - webhookService.deliver(payload, WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO delivery = webhookService.ping(webhook.getId()); assertEquals(200, delivery.getHttpStatusCode()); trustedHttpsServer.verify( postRequestedFor(urlEqualTo("/test-endpoint")) .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(matchingJsonPath("$.type", equalTo(WebhookEventType.CUSTOM.value()))) - .withRequestBody(matchingJsonPath("$.timestamp")) - .withRequestBody(matchingJsonPath("$.data.data", equalTo("success")))); + .withRequestBody(matchingJsonPath("$.type", equalTo(WebhookEventType.PING.value()))) + .withRequestBody(matchingJsonPath("$.timestamp"))); } @Test @@ -98,10 +94,7 @@ void deliver_sslVerificationEnabled_withUntrustedAndHostnameMismatchCert_doesNot .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - String payload = "{\"data\":\"success\"}"; - - DeliveryDTO delivery = - webhookService.deliver(payload, WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO delivery = webhookService.ping(webhook.getId()); assertNull(delivery.getHttpStatusCode()); assertEquals("SSL certificate validation failed", delivery.getErrorMessage()); @@ -121,18 +114,14 @@ void deliver_sslVerificationDisabled_withTrustedCert_sendsRequest() { .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - String payload = "{\"data\":\"success\"}"; - - DeliveryDTO delivery = - webhookService.deliver(payload, WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO delivery = webhookService.ping(webhook.getId()); assertEquals(200, delivery.getHttpStatusCode()); trustedHttpsServer.verify( postRequestedFor(urlEqualTo("/test-endpoint")) .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(matchingJsonPath("$.type", equalTo(WebhookEventType.CUSTOM.value()))) - .withRequestBody(matchingJsonPath("$.timestamp")) - .withRequestBody(matchingJsonPath("$.data.data", equalTo("success")))); + .withRequestBody(matchingJsonPath("$.type", equalTo(WebhookEventType.PING.value()))) + .withRequestBody(matchingJsonPath("$.timestamp"))); } @Test @@ -149,17 +138,13 @@ void deliver_sslVerificationDisabled_withUntrustedAndHostnameMismatchCert_sendsR .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - String payload = "{\"data\":\"success\"}"; - - DeliveryDTO delivery = - webhookService.deliver(payload, WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO delivery = webhookService.ping(webhook.getId()); assertEquals(200, delivery.getHttpStatusCode()); untrustedHttpsServer.verify( postRequestedFor(urlEqualTo("/test-endpoint")) .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(matchingJsonPath("$.type", equalTo(WebhookEventType.CUSTOM.value()))) - .withRequestBody(matchingJsonPath("$.timestamp")) - .withRequestBody(matchingJsonPath("$.data.data", equalTo("success")))); + .withRequestBody(matchingJsonPath("$.type", equalTo(WebhookEventType.PING.value()))) + .withRequestBody(matchingJsonPath("$.timestamp"))); } } diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliveryTransactionBoundaryIntegrationTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliveryTransactionBoundaryIntegrationTest.java index 684062543..1127f807b 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliveryTransactionBoundaryIntegrationTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliveryTransactionBoundaryIntegrationTest.java @@ -18,7 +18,6 @@ import eu.bbmri_eric.negotiator.webhook.Webhook; import eu.bbmri_eric.negotiator.webhook.WebhookRepository; import eu.bbmri_eric.negotiator.webhook.WebhookService; -import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType; import java.time.Duration; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -61,15 +60,9 @@ void deliver_withTwoSlowDeliveries_allowsConcurrentRepositoryRead( try (ExecutorService executor = Executors.newFixedThreadPool(3)) { Future firstDeliveryFuture = - executor.submit( - () -> - webhookService.deliver( - "{\"delivery\":1}", WebhookEventType.CUSTOM, firstWebhook.getId())); + executor.submit(() -> webhookService.ping(firstWebhook.getId())); Future secondDeliveryFuture = - executor.submit( - () -> - webhookService.deliver( - "{\"delivery\":2}", WebhookEventType.CUSTOM, secondWebhook.getId())); + executor.submit(() -> webhookService.ping(secondWebhook.getId())); await("both delivery attempts") .atMost(Duration.ofSeconds(2)) diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliveryTransportFaultIntegrationTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliveryTransportFaultIntegrationTest.java index 0d18e364d..6703183b9 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliveryTransportFaultIntegrationTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookDeliveryTransportFaultIntegrationTest.java @@ -15,7 +15,6 @@ import eu.bbmri_eric.negotiator.webhook.Webhook; import eu.bbmri_eric.negotiator.webhook.WebhookRepository; import eu.bbmri_eric.negotiator.webhook.WebhookService; -import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,10 +51,7 @@ void deliver_withResponseTimeout_setsNullStatusAndTimeoutMessage() { wireMockServer.stubFor( post(urlEqualTo("/slow-endpoint")).willReturn(ok().withFixedDelay(3000).withBody("ok"))); - String payload = "{\"data\":\"success\"}"; - - DeliveryDTO delivery = - webhookService.deliver(payload, WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO delivery = webhookService.ping(webhook.getId()); assertNull(delivery.getHttpStatusCode()); assertEquals("Request timeout", delivery.getErrorMessage()); @@ -71,10 +67,7 @@ void deliver_withWireMockFault_setsErrorForAllFaultOptions(Fault fault) { wireMockServer.stubFor( post(urlEqualTo("/fault-endpoint")).willReturn(ok().withFault(fault).withBody("fault"))); - String payload = "{\"data\":\"fault\"}"; - - DeliveryDTO delivery = - webhookService.deliver(payload, WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO delivery = webhookService.ping(webhook.getId()); assertTrue(delivery.getHttpStatusCode() == null || delivery.getHttpStatusCode() >= 500); assertTrue(StringUtils.isNotBlank(delivery.getErrorMessage())); @@ -88,10 +81,7 @@ void deliver_withUnexpectedRuntimeException_setsNullStatusAndErrorMessage() { Webhook webhook = webhookRepository.save(new Webhook("http://localhost/{missingVar}", true, true)); - String payload = "{\"data\":\"runtime\"}"; - - DeliveryDTO delivery = - webhookService.deliver(payload, WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO delivery = webhookService.ping(webhook.getId()); assertNull(delivery.getHttpStatusCode()); assertTrue(StringUtils.isNotBlank(delivery.getErrorMessage())); diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookRedeliveryIntegrationTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookRedeliveryIntegrationTest.java index 59c842b0c..c4298da75 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookRedeliveryIntegrationTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookRedeliveryIntegrationTest.java @@ -58,10 +58,7 @@ void redeliver_redeliveryOfRedelivery_preservesOriginalRootId(WireMockRuntimeInf .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - String payload = "{\"data\":\"redelivery\"}"; - - DeliveryDTO firstDelivery = - webhookService.deliver(payload, WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO firstDelivery = webhookService.ping(webhook.getId()); long firstAttemptTimestamp = getAllServeEvents().stream() @@ -91,9 +88,8 @@ void redeliver_redeliveryOfRedelivery_preservesOriginalRootId(WireMockRuntimeInf 3, postRequestedFor(urlEqualTo("/redeliver-endpoint")) .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(matchingJsonPath("$.type", equalTo(WebhookEventType.CUSTOM.value()))) - .withRequestBody(matchingJsonPath("$.timestamp")) - .withRequestBody(matchingJsonPath("$.data.data", equalTo("redelivery")))); + .withRequestBody(matchingJsonPath("$.type", equalTo(WebhookEventType.PING.value()))) + .withRequestBody(matchingJsonPath("$.timestamp"))); var serveEvents = getAllServeEvents().stream() diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookSignatureReferenceVerificationIntegrationTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookSignatureReferenceVerificationIntegrationTest.java index 2503124f0..a881e5ca0 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookSignatureReferenceVerificationIntegrationTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/integration/service/WebhookSignatureReferenceVerificationIntegrationTest.java @@ -21,7 +21,6 @@ import eu.bbmri_eric.negotiator.webhook.WebhookRepository; import eu.bbmri_eric.negotiator.webhook.WebhookSecretService; import eu.bbmri_eric.negotiator.webhook.WebhookService; -import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; @@ -65,8 +64,7 @@ void deliver_withSecret_emitsSignatureVerifiedByReferenceImplementation( .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"status\":\"received\"}"))); - DeliveryDTO delivery = - webhookService.deliver("{\"data\":\"signed\"}", WebhookEventType.CUSTOM, webhook.getId()); + DeliveryDTO delivery = webhookService.ping(webhook.getId()); assertEquals(200, delivery.getHttpStatusCode()); verify(1, postRequestedFor(urlEqualTo("/signed-endpoint"))); diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/unit/model/WebhookEntityTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/unit/model/WebhookEntityTest.java index 2d0cecec3..7d62f68de 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/unit/model/WebhookEntityTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/unit/model/WebhookEntityTest.java @@ -25,7 +25,7 @@ void setUp() { @Test void testAddSingleDeliveryWith200() { Delivery delivery = - new Delivery("{\"message\":\"Test delivery 1\"}", 200, WebhookEventType.CUSTOM); + new Delivery("{\"message\":\"Test delivery 1\"}", 200, WebhookEventType.PING); webhook.addDelivery(delivery); assertEquals(1, webhook.getDeliveries().size()); assertEquals(webhook.getId(), delivery.getWebhookId()); @@ -36,7 +36,7 @@ void testAddSingleDeliveryWith200() { void testAddMultipleDeliveriesWithinLimit() { for (int i = 1; i <= 5; i++) { Delivery delivery = - new Delivery("{\"message\":\"Delivery " + i + "\"}", 200, WebhookEventType.CUSTOM); + new Delivery("{\"message\":\"Delivery " + i + "\"}", 200, WebhookEventType.PING); webhook.addDelivery(delivery); } assertEquals(5, webhook.getDeliveries().size()); @@ -51,7 +51,7 @@ void testAddMultipleDeliveriesWithinLimit() { void testAddMoreThan100Deliveries() { for (int i = 1; i <= 105; i++) { Delivery delivery = - new Delivery("{\"message\":\"Delivery " + i + "\"}", 200, WebhookEventType.CUSTOM); + new Delivery("{\"message\":\"Delivery " + i + "\"}", 200, WebhookEventType.PING); webhook.addDelivery(delivery); } assertEquals(100, webhook.getDeliveries().size()); @@ -64,7 +64,7 @@ void testAddDeliveryNon200WithoutErrorMessageThrowsException() { IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> new Delivery("{\"message\":\"Error delivery\"}", 500, WebhookEventType.CUSTOM)); + () -> new Delivery("{\"message\":\"Error delivery\"}", 500, WebhookEventType.PING)); assertEquals("Non-200 HTTP status code requires an error message.", exception.getMessage()); } @@ -72,7 +72,7 @@ void testAddDeliveryNon200WithoutErrorMessageThrowsException() { void testAddDeliveryNon200WithErrorMessage() { Delivery delivery = new Delivery( - "{\"message\":\"Error delivery\"}", 500, "Server error", WebhookEventType.CUSTOM); + "{\"message\":\"Error delivery\"}", 500, "Server error", WebhookEventType.PING); webhook.addDelivery(delivery); assertEquals(1, webhook.getDeliveries().size()); assertEquals("Server error", delivery.getErrorMessage()); @@ -81,11 +81,11 @@ void testAddDeliveryNon200WithErrorMessage() { @Test void testDeliveriesOrderedByAtDescending() { - Delivery d1 = new Delivery("{\"message\":\"Oldest\"}", 200, WebhookEventType.CUSTOM); + Delivery d1 = new Delivery("{\"message\":\"Oldest\"}", 200, WebhookEventType.PING); d1.setAt(LocalDateTime.now().minusMinutes(10)); - Delivery d2 = new Delivery("{\"message\":\"Middle\"}", 200, WebhookEventType.CUSTOM); + Delivery d2 = new Delivery("{\"message\":\"Middle\"}", 200, WebhookEventType.PING); d2.setAt(LocalDateTime.now().minusMinutes(5)); - Delivery d3 = new Delivery("{\"message\":\"Newest\"}", 200, WebhookEventType.CUSTOM); + Delivery d3 = new Delivery("{\"message\":\"Newest\"}", 200, WebhookEventType.PING); d3.setAt(LocalDateTime.now()); webhook.addDelivery(d1); webhook.addDelivery(d2); diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryDispatcherTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryDispatcherTest.java index fe4a6fdea..fcb6edb7f 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryDispatcherTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryDispatcherTest.java @@ -3,7 +3,6 @@ import static org.mockito.Mockito.verify; import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType; -import java.time.Instant; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -24,12 +23,10 @@ void setUp() { @Test void scheduleDelivery_delegatesToWebhookService() { - Instant occurredAt = Instant.parse("2026-01-01T00:00:00Z"); - dispatcher.scheduleDelivery( - 1L, "{\"key\":\"value\"}", WebhookEventType.NEGOTIATION_INFO_UPDATED, occurredAt); + 1L, "{\"key\":\"value\"}", WebhookEventType.NEGOTIATION_INFO_UPDATED); verify(webhookService) - .deliver("{\"key\":\"value\"}", WebhookEventType.NEGOTIATION_INFO_UPDATED, 1L, occurredAt); + .deliver("{\"key\":\"value\"}", WebhookEventType.NEGOTIATION_INFO_UPDATED, 1L); } } diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryPersisterTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryPersisterTest.java index 6da1915d0..7fa0a4d93 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryPersisterTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookDeliveryPersisterTest.java @@ -37,7 +37,7 @@ void persist_whenWebhookExists_addsDeliveryAndReturnsMappedDto() { Long webhookId = 1L; Webhook webhook = new Webhook("https://example.com/webhook", true, true); webhook.setId(webhookId); - Delivery delivery = new Delivery("{\"test\":\"ok\"}", 200, WebhookEventType.CUSTOM); + Delivery delivery = new Delivery("{\"test\":\"ok\"}", 200, WebhookEventType.PING); DeliveryDTO expected = new DeliveryDTO(); when(webhookRepository.findById(webhookId)).thenReturn(Optional.of(webhook)); @@ -57,7 +57,7 @@ void persist_whenWebhookExists_addsDeliveryAndReturnsMappedDto() { void persist_whenWebhookDoesNotExist_throwsEntityNotFoundException() { Long webhookId = 99L; Delivery delivery = - new Delivery("{\"test\":\"not-found\"}", 500, "error", WebhookEventType.CUSTOM); + new Delivery("{\"test\":\"not-found\"}", 500, "error", WebhookEventType.PING); when(webhookRepository.findById(webhookId)).thenReturn(Optional.empty()); diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookEventListenerTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookEventListenerTest.java index fc7e9fd8a..b41b875a8 100644 --- a/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookEventListenerTest.java +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookEventListenerTest.java @@ -48,7 +48,7 @@ void onWebhookEvent_whenEventTypeIsNotDispatched_shouldIgnoreEvent() webhookEventListener.onWebhookEvent(unsupportedEvent); verify(objectMapper, never()).writeValueAsString(any()); - verify(webhookDeliveryDispatcher, never()).scheduleDelivery(any(), any(), any(), any()); + verify(webhookDeliveryDispatcher, never()).scheduleDelivery(any(), any(), any()); } @Test @@ -66,7 +66,7 @@ void onWebhookEvent_whenPayloadSerializationFails_shouldNotDeliverWebhook() webhookEventListener.onWebhookEvent(event); - verify(webhookDeliveryDispatcher, never()).scheduleDelivery(any(), any(), any(), any()); + verify(webhookDeliveryDispatcher, never()).scheduleDelivery(any(), any(), any()); } @Test @@ -90,14 +90,12 @@ void onWebhookEvent_whenEventIsDispatched_schedulesOneDeliveryPerActiveWebhook() .scheduleDelivery( 1L, "{\"type\":\"negotiation.info.updated\",\"timestamp\":\"2026-01-01T00:00:00Z\",\"data\":{\"negotiationId\":\"negotiation-1\"}}", - WebhookEventType.NEGOTIATION_INFO_UPDATED, - Instant.parse("2026-01-01T00:00:00Z")); + WebhookEventType.NEGOTIATION_INFO_UPDATED); verify(webhookDeliveryDispatcher) .scheduleDelivery( 2L, "{\"type\":\"negotiation.info.updated\",\"timestamp\":\"2026-01-01T00:00:00Z\",\"data\":{\"negotiationId\":\"negotiation-1\"}}", - WebhookEventType.NEGOTIATION_INFO_UPDATED, - Instant.parse("2026-01-01T00:00:00Z")); + WebhookEventType.NEGOTIATION_INFO_UPDATED); } @Test @@ -116,6 +114,6 @@ void onWebhookEvent_whenNoActiveWebhooks_schedulesNoDeliveries() throws JsonProc webhookEventListener.onWebhookEvent(event); - verify(webhookDeliveryDispatcher, never()).scheduleDelivery(any(), any(), any(), any()); + verify(webhookDeliveryDispatcher, never()).scheduleDelivery(any(), any(), any()); } } diff --git a/frontend/src/store/admin.js b/frontend/src/store/admin.js index 636bb55b0..e6d299991 100644 --- a/frontend/src/store/admin.js +++ b/frontend/src/store/admin.js @@ -106,18 +106,14 @@ export const useAdminStore = defineStore('admin', () => { function testWebhook(webhookId) { return axios - .post( - `${apiPaths.BASE_API_PATH}/webhooks/${webhookId}/deliveries`, - { test: 'yes' }, - { - headers: getBearerHeaders(), - }, - ) + .post(`${apiPaths.BASE_API_PATH}/webhooks/${webhookId}/ping`, null, { + headers: getBearerHeaders(), + }) .then((response) => { return response.data }) .catch((error) => { - notifications.setNotification('Failed to send test delivery', 'danger') + notifications.setNotification('Failed to send test ping', 'danger') throw error }) } From 22faec932c91e9266db45df583e2c22150f93d9a Mon Sep 17 00:00:00 2001 From: Markus Stetschnig Date: Wed, 13 May 2026 11:01:06 +0200 Subject: [PATCH 2/2] test: serialization failure on webhook ping --- .../webhook/WebhookServiceImplTest.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookServiceImplTest.java diff --git a/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookServiceImplTest.java b/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookServiceImplTest.java new file mode 100644 index 000000000..9cff6cc68 --- /dev/null +++ b/backend/src/test/java/eu/bbmri_eric/negotiator/webhook/WebhookServiceImplTest.java @@ -0,0 +1,87 @@ +package eu.bbmri_eric.negotiator.webhook; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verifyNoInteractions; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.bbmri_eric.negotiator.webhook.event.WebhookEventType; +import eu.bbmri_eric.negotiator.webhook.event.WebhookPayloadEnvelope; +import java.time.Instant; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.modelmapper.ModelMapper; +import org.springframework.web.client.RestTemplate; + +@ExtendWith(MockitoExtension.class) +class WebhookServiceImplTest { + + @Mock private WebhookRepository webhookRepository; + @Mock private DeliveryRepository deliveryRepository; + @Mock private ModelMapper modelMapper; + @Mock private WebhookDeliveryPersister webhookDeliveryPersister; + @Mock private WebhookSecretService webhookSecretService; + @Mock private WebhookSigningService webhookSigningService; + @Mock private RestTemplate secureRestTemplate; + @Mock private RestTemplate insecureRestTemplate; + + private WebhookServiceImpl service; + + @BeforeEach + void setUp() { + service = + new WebhookServiceImpl( + webhookRepository, + deliveryRepository, + modelMapper, + new ObjectMapper(), + webhookDeliveryPersister, + webhookSecretService, + webhookSigningService, + secureRestTemplate, + insecureRestTemplate); + } + + @Test + void ping_whenPayloadEnvelopeIsNotSerializable_throwsIllegalStateException() { + Long webhookId = 1L; + var payloadEnvelope = + new WebhookPayloadEnvelope<>( + WebhookEventType.PING, Instant.now(), new NonSerializablePing()); + + try (MockedStatic mockedPing = mockStatic(WebhookPayloadEnvelope.class)) { + mockedPing + .when(() -> WebhookPayloadEnvelope.ping(eq(webhookId), any(Instant.class))) + .thenReturn(payloadEnvelope); + + IllegalStateException ex = + assertThrows(IllegalStateException.class, () -> service.ping(webhookId)); + + assertEquals("Could not serialize webhook payload", ex.getMessage()); + assertInstanceOf(JsonProcessingException.class, ex.getCause()); + } + + verifyNoInteractions( + webhookRepository, + deliveryRepository, + webhookDeliveryPersister, + secureRestTemplate, + insecureRestTemplate); + } + + @SuppressWarnings("unused") + private static final class NonSerializablePing { + public String getWebhookId() { + throw new UnsupportedOperationException("Cannot serialize ping payload"); + } + } +}