diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/AlertReportController.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/AlertReportController.java index 0f5a19d1d63a..704fc064829f 100644 --- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/AlertReportController.java +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/AlertReportController.java @@ -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; @@ -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); } diff --git a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/AlertReportRequest.java b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/AlertReportRequest.java new file mode 100644 index 000000000000..4b486819c69c --- /dev/null +++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/AlertReportRequest.java @@ -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 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 getLabels() { + return labels; + } + + public void setLabels(final Map 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; + } +} diff --git a/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/AlertReportControllerTest.java b/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/AlertReportControllerTest.java new file mode 100644 index 000000000000..ac2803a9118b --- /dev/null +++ b/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/AlertReportControllerTest.java @@ -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 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")); + } +} diff --git a/shenyu-alert/src/main/java/org/apache/shenyu/alert/strategy/EmailAlertNotifyStrategy.java b/shenyu-alert/src/main/java/org/apache/shenyu/alert/strategy/EmailAlertNotifyStrategy.java index 658e847f510d..748b7c2bf6ea 100644 --- a/shenyu-alert/src/main/java/org/apache/shenyu/alert/strategy/EmailAlertNotifyStrategy.java +++ b/shenyu-alert/src/main/java/org/apache/shenyu/alert/strategy/EmailAlertNotifyStrategy.java @@ -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); diff --git a/shenyu-alert/src/test/java/org/apache/shenyu/alert/strategy/EmailAlertNotifyStrategyTest.java b/shenyu-alert/src/test/java/org/apache/shenyu/alert/strategy/EmailAlertNotifyStrategyTest.java new file mode 100644 index 000000000000..c039a2d556b7 --- /dev/null +++ b/shenyu-alert/src/test/java/org/apache/shenyu/alert/strategy/EmailAlertNotifyStrategyTest.java @@ -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("Rendered mailAlarm template"); + + 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)); + } +} diff --git a/shenyu-common/src/test/java/org/apache/shenyu/common/dto/AlarmContentTest.java b/shenyu-common/src/test/java/org/apache/shenyu/common/dto/AlarmContentTest.java new file mode 100644 index 000000000000..8be103714b0b --- /dev/null +++ b/shenyu-common/src/test/java/org/apache/shenyu/common/dto/AlarmContentTest.java @@ -0,0 +1,49 @@ +/* + * 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.common.dto; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case for AlarmContent. + */ +public class AlarmContentTest { + + @Test + public void testBuilderAndGetterSetter() { + AlarmContent alarmContent = new AlarmContent.Builder() + .title("test title") + .content("test content") + .level((byte) 1) + .build(); + + assertTrue("test title".equals(alarmContent.getTitle())); + assertTrue("test content".equals(alarmContent.getContent())); + assertTrue(alarmContent.getLevel() == 1); + + alarmContent.setTitle("new title"); + alarmContent.setContent("new content"); + alarmContent.setLevel((byte) 2); + + assertTrue("new title".equals(alarmContent.getTitle())); + assertTrue("new content".equals(alarmContent.getContent())); + assertTrue(alarmContent.getLevel() == 2); + } +}