diff --git a/common/src/main/java/org/apache/hertzbeat/common/constants/PluginType.java b/common/src/main/java/org/apache/hertzbeat/common/constants/PluginType.java new file mode 100644 index 00000000000..d586889cec6 --- /dev/null +++ b/common/src/main/java/org/apache/hertzbeat/common/constants/PluginType.java @@ -0,0 +1,29 @@ +/* + * 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.hertzbeat.common.constants; + +/** + * plugin type + */ +public enum PluginType { + + /** + * do something after alter + */ + POST_ALERT +} diff --git a/common/src/main/java/org/apache/hertzbeat/common/entity/dto/PluginUpload.java b/common/src/main/java/org/apache/hertzbeat/common/entity/dto/PluginUpload.java index 1a5c755679c..426834c5fc9 100644 --- a/common/src/main/java/org/apache/hertzbeat/common/entity/dto/PluginUpload.java +++ b/common/src/main/java/org/apache/hertzbeat/common/entity/dto/PluginUpload.java @@ -19,13 +19,17 @@ import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.web.multipart.MultipartFile; /** * data transfer class for uploading plugins */ @Data +@AllArgsConstructor +@NoArgsConstructor public class PluginUpload { @NotNull diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/controller/PluginController.java b/manager/src/main/java/org/apache/hertzbeat/manager/controller/PluginController.java index b92a07576a4..b75f6973841 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/controller/PluginController.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/controller/PluginController.java @@ -105,6 +105,6 @@ public ResponseEntity> deleteTags( @Operation(summary = "Update enable status", description = "Delete plugins based on ID") public ResponseEntity> updatePluginStatus(@RequestBody PluginMetadata plugin) { pluginService.updateStatus(plugin); - return ResponseEntity.ok(Message.success("Delete success")); + return ResponseEntity.ok(Message.success("Update success")); } } diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/PluginService.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/PluginService.java index d9c3e85b0e4..2fe3e5041c5 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/service/PluginService.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/PluginService.java @@ -17,12 +17,9 @@ package org.apache.hertzbeat.manager.service; -import java.io.File; -import java.util.List; import java.util.Set; import java.util.function.Consumer; import org.apache.hertzbeat.common.entity.dto.PluginUpload; -import org.apache.hertzbeat.common.entity.manager.PluginItem; import org.apache.hertzbeat.common.entity.manager.PluginMetadata; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -33,21 +30,6 @@ */ public interface PluginService { - /** - * load jar to classloader - */ - void loadJarToClassLoader(); - - - /** - * verify the type of the jar package - * - * @param jarFile jar file - * @return return the full path of the Plugin interface implementation class - */ - List validateJarFile(File jarFile); - - /** * save plugin */ diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/PluginServiceImpl.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/PluginServiceImpl.java index f4e7cd78a95..d5de0e5889f 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/PluginServiceImpl.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/PluginServiceImpl.java @@ -65,6 +65,18 @@ @RequiredArgsConstructor public class PluginServiceImpl implements PluginService { + private final PluginMetadataDao metadataDao; + + private final PluginItemDao itemDao; + + public static Map, PluginType> PLUGIN_TYPE_MAPPING = new HashMap<>(); + + /** + * plugin status + */ + private static final Map PLUGIN_ENABLE_STATUS = new ConcurrentHashMap<>(); + + private URLClassLoader pluginClassLoader; @Override @@ -102,21 +114,18 @@ public void updateStatus(PluginMetadata plugin) { } } - public static Map, PluginType> PLUGIN_TYPE_MAPPING = new HashMap<>(); static { PLUGIN_TYPE_MAPPING.put(Plugin.class, PluginType.POST_ALERT); } - private final PluginMetadataDao metadataDao; - private final PluginItemDao itemDao; /** - * plugin status + * verify the type of the jar package + * + * @param jarFile jar file + * @return return the full path of the Plugin interface implementation class */ - private static final Map PLUGIN_ENABLE_STATUS = new ConcurrentHashMap<>(); - - @Override public List validateJarFile(File jarFile) { List pluginItems = new ArrayList<>(); try { @@ -223,9 +232,11 @@ private void syncPluginStatus() { PLUGIN_ENABLE_STATUS.putAll(statusMap); } + /** + * load jar to classloader + */ @PostConstruct - @Override - public void loadJarToClassLoader() { + private void loadJarToClassLoader() { try { if (pluginClassLoader != null) { pluginClassLoader.close(); diff --git a/manager/src/test/java/org/apache/hertzbeat/manager/controller/PluginControllerTest.java b/manager/src/test/java/org/apache/hertzbeat/manager/controller/PluginControllerTest.java new file mode 100644 index 00000000000..eb3ad88a94e --- /dev/null +++ b/manager/src/test/java/org/apache/hertzbeat/manager/controller/PluginControllerTest.java @@ -0,0 +1,116 @@ +/* + * 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.hertzbeat.manager.controller; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.ArrayList; +import java.util.List; +import org.apache.hertzbeat.common.constants.CommonConstants; +import org.apache.hertzbeat.common.entity.manager.PluginMetadata; +import org.apache.hertzbeat.common.util.JsonUtil; +import org.apache.hertzbeat.manager.service.PluginService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +/** + * test case for plugin controller + */ +@ExtendWith(MockitoExtension.class) +class PluginControllerTest { + + private MockMvc mockMvc; + + @InjectMocks + private PluginController pluginController; + + @Mock + private PluginService pluginService; + + @BeforeEach + void setUp() { + this.mockMvc = MockMvcBuilders.standaloneSetup(pluginController).build(); + } + + @Test + void uploadNewPlugin() throws Exception { + MockMultipartFile jarFile = new MockMultipartFile( + "jarFile", + "plugin-test.jar", + "application/java-archive", + "This is the file content".getBytes() + ); + + this.mockMvc.perform(MockMvcRequestBuilders.multipart("/api/plugin") + .file(jarFile) + .contentType(MediaType.MULTIPART_FORM_DATA) + .param("name", "test-plugin") + .param("enableStatus", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE)) + .andExpect(jsonPath("$.msg").value("Add success")) + .andReturn(); + } + + @Test + void getPlugins() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/plugin?&search={search}", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE)) + .andReturn(); + } + + @Test + void deleteTags() throws Exception { + List ids = new ArrayList<>(); + ids.add(6565463543L); + + this.mockMvc.perform(MockMvcRequestBuilders.delete("/api/plugin") + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.toJson(ids))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE)) + .andExpect(jsonPath("$.msg").value("Delete success")) + .andReturn(); + } + + @Test + void updatePluginStatus() throws Exception { + PluginMetadata metadata = new PluginMetadata(); + metadata.setId(6565463543L); + metadata.setEnableStatus(true); + + this.mockMvc.perform(MockMvcRequestBuilders.put("/api/plugin") + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtil.toJson(metadata))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE)) + .andExpect(jsonPath("$.msg").value("Update success")) + .andReturn(); + } + +} diff --git a/manager/src/test/java/org/apache/hertzbeat/manager/service/PluginServiceTest.java b/manager/src/test/java/org/apache/hertzbeat/manager/service/PluginServiceTest.java new file mode 100644 index 00000000000..238b47b068e --- /dev/null +++ b/manager/src/test/java/org/apache/hertzbeat/manager/service/PluginServiceTest.java @@ -0,0 +1,135 @@ +/* + * 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.hertzbeat.manager.service; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.apache.hertzbeat.common.constants.PluginType; +import org.apache.hertzbeat.common.entity.dto.PluginUpload; +import org.apache.hertzbeat.common.entity.manager.PluginItem; +import org.apache.hertzbeat.common.entity.manager.PluginMetadata; +import org.apache.hertzbeat.manager.dao.PluginItemDao; +import org.apache.hertzbeat.manager.dao.PluginMetadataDao; +import org.apache.hertzbeat.manager.service.impl.PluginServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.mock.web.MockMultipartFile; + +/** + * Test case for {@link PluginService} + */ +@ExtendWith(MockitoExtension.class) +class PluginServiceTest { + + @InjectMocks + private PluginServiceImpl pluginService; + @Mock + private PluginMetadataDao metadataDao; + + @Mock + private PluginItemDao itemDao; + + + @BeforeEach + void setUp() { + pluginService = new PluginServiceImpl(metadataDao, itemDao); + } + + @Test + void testSavePlugin(){ + + List pluginItems = Collections.singletonList(new PluginItem("org.apache.hertzbear.PluginTest", PluginType.POST_ALERT)); + PluginServiceImpl service = spy(pluginService); + doReturn(pluginItems).when(service).validateJarFile(any()); + + MockMultipartFile mockFile = new MockMultipartFile("file", "test-plugin.jar", "application/java-archive", "plugin-content".getBytes()); + PluginUpload pluginUpload = new PluginUpload(mockFile, "Test Plugin", true); + + when(metadataDao.save(any(PluginMetadata.class))).thenReturn(new PluginMetadata()); + when(itemDao.saveAll(anyList())).thenReturn(Collections.emptyList()); + + service.savePlugin(pluginUpload); + verify(metadataDao, times(1)).save(any(PluginMetadata.class)); + verify(itemDao, times(1)).saveAll(anyList()); + + } + + @Test + void testUpdateStatus() { + PluginMetadata plugin = new PluginMetadata(); + plugin.setId(1L); + plugin.setEnableStatus(true); + plugin.setName("test-plugin"); + + when(metadataDao.findById(1L)).thenReturn(Optional.of(plugin)); + when(metadataDao.save(any(PluginMetadata.class))).thenReturn(plugin); + assertDoesNotThrow(() -> pluginService.updateStatus(plugin)); + } + + @Test + void testDeletePlugins() { + PluginMetadata plugin = new PluginMetadata(); + plugin.setId(1L); + plugin.setJarFilePath("path/to/plugin.jar"); + Set ids = new HashSet<>(Collections.singletonList(1L)); + + when(metadataDao.findAllById(ids)).thenReturn(Collections.singletonList(plugin)); + doNothing().when(metadataDao).deleteById(anyLong()); + + pluginService.deletePlugins(ids); + verify(metadataDao, times(1)).deleteById(1L); + } + + @Test + void testGetPlugins() { + Specification spec = mock(Specification.class); + PageRequest pageRequest = PageRequest.of(0, 10); + Page page = new PageImpl<>(Collections.singletonList(new PluginMetadata())); + + when(metadataDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(page); + + Page result = pluginService.getPlugins(spec, pageRequest); + assertFalse(result.isEmpty()); + verify(metadataDao, times(1)).findAll(any(Specification.class), any(PageRequest.class)); + } + +}