diff --git a/pom.xml b/pom.xml
index c8a4e42..815011f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -109,6 +109,12 @@
spring-boot-starter-test
test
+
+ org.springframework.cloud
+ spring-cloud-contract-wiremock
+ 4.1.4
+ test
+
diff --git a/src/main/java/dev/vality/alerting/tg/bot/controller/WebhookController.java b/src/main/java/dev/vality/alerting/tg/bot/controller/WebhookController.java
index 5bc942c..3f22fab 100644
--- a/src/main/java/dev/vality/alerting/tg/bot/controller/WebhookController.java
+++ b/src/main/java/dev/vality/alerting/tg/bot/controller/WebhookController.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.vality.alerting.tg.bot.config.properties.AlertmanagerWebhookProperties;
import dev.vality.alerting.tg.bot.model.Webhook;
+import dev.vality.alerting.tg.bot.service.AlertBot;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
@@ -22,7 +23,7 @@
public class WebhookController {
private final AlertmanagerWebhookProperties alertmanagerWebhookProperties;
private final ObjectMapper objectMapper;
- public static final String FIRING = "firing";
+ private final AlertBot alertBot;
@PostMapping(value = "/webhook", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity processWebhook(HttpServletRequest servletRequest) {
@@ -30,6 +31,7 @@ public ResponseEntity processWebhook(HttpServletRequest servletRequest)
var webhookBody = servletRequest.getReader().lines().collect(Collectors.joining(" "));
log.info("Received webhook from alertmanager: {}", webhookBody);
var webhook = objectMapper.readValue(webhookBody, Webhook.class);
+ alertBot.sendAlertMessage(webhook);
} catch (Exception e) {
log.error("Unexpected error during webhook parsing:", e);
return ResponseEntity.internalServerError().build();
diff --git a/src/main/java/dev/vality/alerting/tg/bot/model/Webhook.java b/src/main/java/dev/vality/alerting/tg/bot/model/Webhook.java
index 19253ed..b426c5f 100644
--- a/src/main/java/dev/vality/alerting/tg/bot/model/Webhook.java
+++ b/src/main/java/dev/vality/alerting/tg/bot/model/Webhook.java
@@ -1,6 +1,8 @@
package dev.vality.alerting.tg.bot.model;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
+import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
@@ -9,6 +11,8 @@
* Alertmanager webhook body
*/
@Data
+@NoArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
public class Webhook {
private String status;
@@ -16,6 +20,8 @@ public class Webhook {
private List alerts;
@Data
+ @NoArgsConstructor
+ @JsonIgnoreProperties(ignoreUnknown = true)
public static class Alert {
private String status;
diff --git a/src/main/java/dev/vality/alerting/tg/bot/service/AlertBot.java b/src/main/java/dev/vality/alerting/tg/bot/service/AlertBot.java
index 86b7e93..912ab21 100644
--- a/src/main/java/dev/vality/alerting/tg/bot/service/AlertBot.java
+++ b/src/main/java/dev/vality/alerting/tg/bot/service/AlertBot.java
@@ -1,6 +1,7 @@
package dev.vality.alerting.tg.bot.service;
import dev.vality.alerting.tg.bot.config.properties.AlertBotProperties;
+import dev.vality.alerting.tg.bot.model.Webhook;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -68,6 +69,11 @@ public void onUpdateReceived(Update update) {
}
}
+ public void sendAlertMessage(Webhook webhook) {
+ sendResponse(properties.getChatId(), properties.getTopics().getCommands(), webhook.getAlerts().toString(),
+ null);
+ }
+
public void sendScheduledMetrics() {
send5xxErrorsMetrics(properties.getChatId());
sendFailedMachinesMetrics(properties.getChatId());
diff --git a/src/test/java/dev/vality/alerting/tg/bot/WebhookControllerTest.java b/src/test/java/dev/vality/alerting/tg/bot/WebhookControllerTest.java
new file mode 100644
index 0000000..35582e4
--- /dev/null
+++ b/src/test/java/dev/vality/alerting/tg/bot/WebhookControllerTest.java
@@ -0,0 +1,108 @@
+package dev.vality.alerting.tg.bot;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import dev.vality.alerting.tg.bot.config.AlertBotConfig;
+import dev.vality.alerting.tg.bot.config.properties.AlertmanagerWebhookProperties;
+import dev.vality.alerting.tg.bot.controller.WebhookController;
+import dev.vality.alerting.tg.bot.model.Webhook;
+import dev.vality.alerting.tg.bot.service.AlertBot;
+import lombok.val;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.mockito.Mockito.verify;
+
+@SpringBootTest
+@TestPropertySource(properties = {
+ "spring.mvc.pathmatch.matching-strategy=ant_path_matcher",
+ "bot.token=test",
+ "bot.name=vality_alerting_bot",
+ "bot.chatId=1",
+ "bot.topics.commands=1",
+ "bot.topics.errors5xx=2",
+ "bot.topics.altpay-conversion=3",
+ "bot.topics.failed-machines=4",
+ "bot.topics.pending-payments=5"
+})
+public class WebhookControllerTest {
+
+ @MockitoBean
+ AlertmanagerWebhookProperties webhookProperties;
+
+ @MockitoBean
+ AlertBot alertBot;
+
+ @MockitoBean
+ AlertBotConfig alertBotConfig;
+
+ String webhookJson = """
+ {
+ "status": "firing",
+ "receiver": "telegram",
+ "alerts": [
+ {
+ "status": "firing",
+ "labels": {
+ "alertname": "Errors5xxHigh",
+ "severity": "critical",
+ "job": "payments",
+ "namespace": "prod",
+ "service": "payments-api",
+ "instance": "payments-api-1",
+ "pod": "payments-api-1-abc123"
+ },
+ "annotations": {
+ "summary": "HTTP 5xx rate is too high",
+ "description": "Payments API is returning >5% 5xx responses for 5m",
+ "runbook_url": "https://runbook.company/alerts/errors5xx"
+ }
+ },
+ {
+ "status": "resolved",
+ "labels": {
+ "alertname": "AltpayConversionLow",
+ "severity": "warning",
+ "job": "altpay",
+ "namespace": "prod",
+ "service": "altpay-conversion",
+ "pod": "altpay-0-xzy987"
+ },
+ "annotations": {
+ "summary": "Altpay conversion dropped",
+ "description": "Altpay conversion < 2% in last 10m"
+ }
+ }
+ ]
+ }
+ """;
+
+ @Test
+ public void sendTgMessageTest() {
+ ObjectMapper objectMapper = new ObjectMapper();
+ WebhookController webhookController = new WebhookController(webhookProperties, objectMapper, alertBot);
+
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ req.setMethod("POST");
+ req.setRequestURI("/alertmanager/webhook");
+ req.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ req.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ req.setContent(webhookJson.getBytes(StandardCharsets.UTF_8));
+
+ val response = webhookController.processWebhook(req);
+ assertThat(response.getStatusCode().value()).isEqualTo(200);
+
+ ArgumentCaptor webhookCaptor = ArgumentCaptor.forClass(Webhook.class);
+ verify(alertBot).sendAlertMessage(webhookCaptor.capture());
+ Webhook passed = webhookCaptor.getValue();
+ assertThat(passed).isNotNull();
+ assertThat(passed.getAlerts()).isNotNull();
+ }
+}