Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<version>4.1.4</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,14 +23,15 @@
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<String> processWebhook(HttpServletRequest servletRequest) {
try {
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();
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/dev/vality/alerting/tg/bot/model/Webhook.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,13 +11,17 @@
* Alertmanager webhook body
*/
@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class Webhook {

private String status;
private String receiver;
private List<Alert> alerts;

@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Alert {

private String status;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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());
Expand Down
108 changes: 108 additions & 0 deletions src/test/java/dev/vality/alerting/tg/bot/WebhookControllerTest.java
Original file line number Diff line number Diff line change
@@ -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<Webhook> webhookCaptor = ArgumentCaptor.forClass(Webhook.class);
verify(alertBot).sendAlertMessage(webhookCaptor.capture());
Webhook passed = webhookCaptor.getValue();
assertThat(passed).isNotNull();
assertThat(passed.getAlerts()).isNotNull();
}
}
Loading