Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cbe08cf
fix(alert): guard against null alert/dateCreated in email alert template
wy471x Jul 25, 2026
df686c5
Merge branch 'master' into fix_EmailAlertReportFail
Aias00 Jul 28, 2026
d9f29dc
Merge branch 'master' into fix_EmailAlertReportFail
Aias00 Jul 28, 2026
77a01ba
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Jul 29, 2026
9a1b554
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Jul 30, 2026
d6f07aa
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Jul 30, 2026
0bbd102
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Jul 31, 2026
aa0d5a8
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Jul 31, 2026
8f1eb9d
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 1, 2026
7562131
fix(alert): move validation from shared AlarmContent to controller-la…
wy471x Aug 2, 2026
85577f9
fix(alert): drop unreachable null-alert guard, add controller validat…
wy471x Aug 2, 2026
a5de0a8
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 2, 2026
2991e8b
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 2, 2026
731dcf8
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 3, 2026
d2ddec6
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 4, 2026
7271358
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 6, 2026
ddf3e55
fix(common): remove whitespace in the pom file
wy471x Aug 11, 2026
8c45151
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 15, 2026
244b2b1
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 17, 2026
ad0eab8
Merge branch 'master' into fix_EmailAlertReportFail
wy471x Aug 23, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.shenyu.admin.controller;

import org.apache.shenyu.admin.aspect.annotation.RestApi;
import org.apache.shenyu.admin.model.dto.AlertReportRequest;
import org.apache.shenyu.admin.model.result.ShenyuAdminResult;
import org.apache.shenyu.admin.service.AlertDispatchService;
import org.apache.shenyu.admin.utils.ShenyuResultMessage;
Expand All @@ -40,11 +41,20 @@ public class AlertReportController {
/**
* report new alert content.
*
* @param alarmContent AlertContentDTO
* @param request alert report request
* @return row int
*/
@PostMapping
public ShenyuAdminResult reportAlert(@Valid @RequestBody final AlarmContent alarmContent) {
public ShenyuAdminResult reportAlert(@Valid @RequestBody final AlertReportRequest request) {
AlarmContent alarmContent = new AlarmContent.Builder()
.title(request.getTitle())
.content(request.getContent())
.level(request.getLevel())
.labels(request.getLabels())
.namespaceId(request.getNamespaceId())
.dateCreated(request.getDateCreated())
.dateUpdated(request.getDateUpdated())
.build();
alertDispatchService.dispatchAlert(alarmContent);
return ShenyuAdminResult.success(ShenyuResultMessage.CREATE_SUCCESS);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shenyu.admin.model.dto;

import jakarta.validation.constraints.NotBlank;
import java.util.Date;
import java.util.Map;

/**
* Request DTO for /alert/report endpoint.
* Validation constraints live here so internal callers using AlarmContent directly are unaffected.
*/
public class AlertReportRequest {

@NotBlank
private String title;

private byte level;

private Map<String, String> labels;

@NotBlank
private String content;

private String namespaceId;

private Date dateCreated;

private Date dateUpdated;

public String getTitle() {
return title;
}

public void setTitle(final String title) {
this.title = title;
}

public byte getLevel() {
return level;
}

public void setLevel(final byte level) {
this.level = level;
}

public Map<String, String> getLabels() {
return labels;
}

public void setLabels(final Map<String, String> labels) {
this.labels = labels;
}

public String getContent() {
return content;
}

public void setContent(final String content) {
this.content = content;
}

public String getNamespaceId() {
return namespaceId;
}

public void setNamespaceId(final String namespaceId) {
this.namespaceId = namespaceId;
}

public Date getDateCreated() {
return dateCreated;
}

public void setDateCreated(final Date dateCreated) {
this.dateCreated = dateCreated;
}

public Date getDateUpdated() {
return dateUpdated;
}

public void setDateUpdated(final Date dateUpdated) {
this.dateUpdated = dateUpdated;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shenyu.admin.controller;

import org.apache.shenyu.admin.service.AlertDispatchService;
import org.apache.shenyu.common.dto.AlarmContent;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
* Test case for AlertReportController.
*/
@ExtendWith(MockitoExtension.class)
public class AlertReportControllerTest {

private MockMvc mockMvc;

@InjectMocks
private AlertReportController alertReportController;

@Mock
private AlertDispatchService alertDispatchService;

@BeforeEach
public void setUp() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
this.mockMvc = MockMvcBuilders.standaloneSetup(alertReportController)
.setValidator(validator)
.build();
}

@Test
public void testBlankTitleReturns400() throws Exception {
String body = "{\"title\":\"\",\"content\":\"test content\",\"level\":1}";

this.mockMvc.perform(post("/alert/report")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest());
}

@Test
public void testNullTitleReturns400() throws Exception {
String body = "{\"content\":\"test content\",\"level\":1}";

this.mockMvc.perform(post("/alert/report")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest());
}

@Test
public void testBlankContentReturns400() throws Exception {
String body = "{\"title\":\"test title\",\"content\":\" \",\"level\":1}";

this.mockMvc.perform(post("/alert/report")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest());
}

@Test
public void testNullContentReturns400() throws Exception {
String body = "{\"title\":\"test title\",\"level\":1}";

this.mockMvc.perform(post("/alert/report")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest());
}

@Test
public void testValidRequestDispatchesCorrectly() throws Exception {
String body = "{\"title\":\"test title\",\"content\":\"test content\",\"level\":1,\"namespaceId\":\"ns-1\",\"labels\":{\"key\":\"value\"}}";

this.mockMvc.perform(post("/alert/report")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isOk());

ArgumentCaptor<AlarmContent> captor = ArgumentCaptor.forClass(AlarmContent.class);
verify(alertDispatchService).dispatchAlert(captor.capture());
AlarmContent dispatched = captor.getValue();
assertEquals("test title", dispatched.getTitle());
assertEquals("test content", dispatched.getContent());
assertEquals((byte) 1, dispatched.getLevel());
assertEquals("ns-1", dispatched.getNamespaceId());
assertEquals("value", dispatched.getLabels().get("key"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,13 @@ public void send(final AlertReceiverDTO receiver, final AlarmContent alert) thro
}

private String buildAlertHtmlTemplate(final AlarmContent alert) {
// Introduce thymeleaf context parameters to render pages
Context context = new Context();
context.setVariable("nameTitle", "ShenYu Alarm");
context.setVariable("nameTriggerTime", "Alarm Time");
context.setVariable("nameContent", "Alarm Content");
context.setVariable("content", alert.getContent());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date alertTime = alert.getDateCreated();
if (Objects.isNull(alert)) {
alertTime = new Date();
}
Date alertTime = Objects.isNull(alert.getDateCreated()) ? new Date() : alert.getDateCreated();
String alarmTime = simpleDateFormat.format(alertTime);
context.setVariable("lastTriggerTime", alarmTime);
return templateEngine.process("mailAlarm", context);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shenyu.alert.strategy;

import org.apache.shenyu.common.dto.AlarmContent;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.thymeleaf.TemplateEngine;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

/**
* Test case for EmailAlertNotifyStrategy.
*/
public class EmailAlertNotifyStrategyTest {

private static Method buildAlertHtmlTemplateMethod;

private static EmailAlertNotifyStrategy strategy;

@BeforeAll
public static void setUp() throws Exception {
TemplateEngine mockEngine = Mockito.mock(TemplateEngine.class);
when(mockEngine.process(eq("mailAlarm"), any(org.thymeleaf.context.IContext.class)))
.thenReturn("<html>Rendered mailAlarm template</html>");

strategy = new EmailAlertNotifyStrategy(mockEngine, null);
buildAlertHtmlTemplateMethod = EmailAlertNotifyStrategy.class
.getDeclaredMethod("buildAlertHtmlTemplate", AlarmContent.class);
buildAlertHtmlTemplateMethod.setAccessible(true);
}

private String invokeBuildAlertHtmlTemplate(final AlarmContent alert) {
try {
return (String) buildAlertHtmlTemplateMethod.invoke(strategy, alert);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}

@Test
public void testNullDateCreatedShouldNotThrowNpe() {
AlarmContent alert = new AlarmContent.Builder()
.title("test title")
.content("test content")
.dateCreated(null)
.build();

assertDoesNotThrow(() -> invokeBuildAlertHtmlTemplate(alert));
}

@Test
public void testValidAlertShouldNotThrow() {
AlarmContent alert = new AlarmContent.Builder()
.title("test title")
.content("test content")
.dateCreated(new Date())
.build();

assertDoesNotThrow(() -> invokeBuildAlertHtmlTemplate(alert));
}

@Test
public void testNullContentShouldNotThrow() {
AlarmContent alert = new AlarmContent.Builder()
.title("test title")
.content(null)
.dateCreated(new Date())
.build();

assertDoesNotThrow(() -> invokeBuildAlertHtmlTemplate(alert));
}
}
Loading
Loading