From 73cea521ebd578e9a66db9cd6cab9bdcf49f614d Mon Sep 17 00:00:00 2001
From: Piotr Gawron <p.gawron@atcomp.pl>
Date: Thu, 16 Jan 2025 16:46:55 +0100
Subject: [PATCH 1/2] basic GET endpoint for plugins

---
 .../persist/dao/plugin/PluginProperty.java    |   8 +
 .../persist/dao/map/CommentDaoTest.java       |   2 +-
 .../services/impl/PluginService.java          |  32 +-
 .../services/interfaces/IPluginService.java   |   9 +-
 .../web/api/plugin/NewPluginController.java   | 134 +++++
 .../web/api/plugin/NewPluginDTO.java          |  90 ++++
 .../web/ControllerIntegrationTest.java        |   4 +
 .../web/PluginControllerIntegrationTest.java  |   3 +-
 .../lcsb/mapviewer/web/api/NewApiDocs.java    |  28 +
 .../api/plugin/NewPluginControllerTest.java   | 486 ++++++++++++++++++
 10 files changed, 788 insertions(+), 8 deletions(-)
 create mode 100644 persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginProperty.java
 create mode 100644 web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginController.java
 create mode 100644 web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginDTO.java
 create mode 100644 web/src/test/java/lcsb/mapviewer/web/api/plugin/NewPluginControllerTest.java

diff --git a/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginProperty.java b/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginProperty.java
new file mode 100644
index 0000000000..073fb582b8
--- /dev/null
+++ b/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginProperty.java
@@ -0,0 +1,8 @@
+package lcsb.mapviewer.persist.dao.plugin;
+
+import lcsb.mapviewer.model.plugin.Plugin;
+import lcsb.mapviewer.persist.dao.MinervaEntityProperty;
+
+public enum PluginProperty implements MinervaEntityProperty<Plugin> {
+  DEFAULT,
+}
diff --git a/persist/src/test/java/lcsb/mapviewer/persist/dao/map/CommentDaoTest.java b/persist/src/test/java/lcsb/mapviewer/persist/dao/map/CommentDaoTest.java
index a0453a3f9e..9eb17973ec 100644
--- a/persist/src/test/java/lcsb/mapviewer/persist/dao/map/CommentDaoTest.java
+++ b/persist/src/test/java/lcsb/mapviewer/persist/dao/map/CommentDaoTest.java
@@ -55,7 +55,7 @@ public class CommentDaoTest extends PersistTestFunctions {
   }
   
   @Test
-  public void testGetById() throws Exception {
+  public void testGetById() {
     Project project = createProject();
 
     int counter = (int) commentDao.getCount();
diff --git a/service/src/main/java/lcsb/mapviewer/services/impl/PluginService.java b/service/src/main/java/lcsb/mapviewer/services/impl/PluginService.java
index 58876fe37d..ed90d60d46 100644
--- a/service/src/main/java/lcsb/mapviewer/services/impl/PluginService.java
+++ b/service/src/main/java/lcsb/mapviewer/services/impl/PluginService.java
@@ -1,17 +1,22 @@
 package lcsb.mapviewer.services.impl;
 
+import lcsb.mapviewer.common.exception.NotImplementedException;
 import lcsb.mapviewer.model.plugin.Plugin;
 import lcsb.mapviewer.model.plugin.PluginDataEntry;
 import lcsb.mapviewer.model.user.User;
 import lcsb.mapviewer.persist.dao.plugin.PluginDao;
 import lcsb.mapviewer.persist.dao.plugin.PluginDataEntryDao;
+import lcsb.mapviewer.persist.dao.plugin.PluginProperty;
 import lcsb.mapviewer.services.interfaces.IPluginService;
 import org.hibernate.Hibernate;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import java.util.Map;
 
 @Transactional
 @Service
@@ -44,6 +49,12 @@ public class PluginService implements IPluginService {
     return result;
   }
 
+  @Override
+  public Page<Plugin> getAll(final Map<PluginProperty, Object> filter, final Pageable pageable) {
+    throw new NotImplementedException();
+  }
+
+
   @Override
   public PluginDataEntry getEntryByKey(final Plugin plugin, final String key, final User user) {
     PluginDataEntry result = pluginDataEntryDao.getByKey(plugin, key, user);
@@ -68,8 +79,8 @@ public class PluginService implements IPluginService {
   }
 
   @Override
-  public Plugin add(final Plugin plugin) {
-    return pluginDao.add(plugin);
+  public void add(final Plugin plugin) {
+    pluginDao.add(plugin);
   }
 
   @Override
@@ -94,4 +105,21 @@ public class PluginService implements IPluginService {
     pluginDao.update(plugin);
   }
 
+
+  @Override
+  public Plugin getById(final int id) {
+    throw new NotImplementedException();
+  }
+
+
+  @Override
+  public void remove(final Plugin object) {
+    throw new NotImplementedException();
+  }
+
+  @Override
+  public long getCount(final Map<PluginProperty, Object> filterOptions) {
+    throw new NotImplementedException();
+  }
+
 }
diff --git a/service/src/main/java/lcsb/mapviewer/services/interfaces/IPluginService.java b/service/src/main/java/lcsb/mapviewer/services/interfaces/IPluginService.java
index afe8939be8..9c98718c79 100644
--- a/service/src/main/java/lcsb/mapviewer/services/interfaces/IPluginService.java
+++ b/service/src/main/java/lcsb/mapviewer/services/interfaces/IPluginService.java
@@ -1,12 +1,13 @@
 package lcsb.mapviewer.services.interfaces;
 
-import java.util.List;
-
 import lcsb.mapviewer.model.plugin.Plugin;
 import lcsb.mapviewer.model.plugin.PluginDataEntry;
 import lcsb.mapviewer.model.user.User;
+import lcsb.mapviewer.persist.dao.plugin.PluginProperty;
+
+import java.util.List;
 
-public interface IPluginService {
+public interface IPluginService extends CrudService<Plugin, PluginProperty> {
 
   Plugin getByHash(final String hash);
 
@@ -18,7 +19,7 @@ public interface IPluginService {
 
   void delete(final Plugin plugin);
 
-  Plugin add(final Plugin plugin);
+  void add(final Plugin plugin);
 
   void add(final PluginDataEntry entry);
 
diff --git a/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginController.java b/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginController.java
new file mode 100644
index 0000000000..4d6558bd47
--- /dev/null
+++ b/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginController.java
@@ -0,0 +1,134 @@
+package lcsb.mapviewer.web.api.plugin;
+
+import lcsb.mapviewer.api.QueryException;
+import lcsb.mapviewer.model.plugin.Plugin;
+import lcsb.mapviewer.model.security.PrivilegeType;
+import lcsb.mapviewer.persist.dao.plugin.PluginProperty;
+import lcsb.mapviewer.services.FailedDependencyException;
+import lcsb.mapviewer.services.ObjectExistsException;
+import lcsb.mapviewer.services.ObjectNotFoundException;
+import lcsb.mapviewer.services.interfaces.IPluginService;
+import lcsb.mapviewer.web.api.NewApiResponseSerializer;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.validator.constraints.NotBlank;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@Validated
+@RequestMapping(
+    value = {
+        "/minerva/new_api/plugins",
+    },
+    produces = MediaType.APPLICATION_JSON_VALUE)
+public class NewPluginController {
+
+  private final Logger logger = LogManager.getLogger();
+
+  private final IPluginService pluginService;
+  private final NewApiResponseSerializer serializer;
+
+  @Autowired
+  public NewPluginController(
+      final IPluginService pluginService,
+      final NewApiResponseSerializer serializer) {
+    this.serializer = serializer;
+    this.pluginService = pluginService;
+  }
+
+  @GetMapping(value = "/{hash}")
+  public ResponseEntity<?> getPlugin(final @NotBlank @PathVariable(value = "hash") String hash)
+      throws ObjectNotFoundException, FailedDependencyException, IOException {
+    final lcsb.mapviewer.model.plugin.Plugin plugin = pluginService.getByHash(hash);
+    return serializer.prepareResponse(plugin);
+  }
+
+  @PostMapping(value = "/")
+  public ResponseEntity<?> addPlugin(
+      final Authentication authentication,
+      final @Valid @RequestBody NewPluginDTO data)
+      throws QueryException, ObjectNotFoundException, ObjectExistsException {
+
+    final boolean isAdmin = authentication.getAuthorities()
+        .contains(new SimpleGrantedAuthority(PrivilegeType.IS_ADMIN.name()));
+
+    Plugin plugin = pluginService.getByHash(data.getHash());
+    if (plugin != null) {
+      throw new ObjectExistsException("Plugin with given hash already exists");
+    }
+    plugin = new Plugin();
+    data.saveToPlugin(plugin, isAdmin);
+    pluginService.add(plugin);
+
+    return serializer.prepareResponse(plugin, HttpStatus.CREATED);
+  }
+
+  @PreAuthorize("hasAnyAuthority('IS_ADMIN')")
+  @PutMapping(value = "/{hash}")
+  public ResponseEntity<?> updatePlugin(
+      final Authentication authentication,
+      final @RequestHeader(name = "If-Match", required = false) String oldETag,
+      final @Valid @RequestBody NewPluginDTO data,
+      final @PathVariable(value = "hash") String hash)
+      throws IOException, QueryException, ObjectNotFoundException, FailedDependencyException {
+    final boolean isAdmin = authentication.getAuthorities()
+        .contains(new SimpleGrantedAuthority(PrivilegeType.IS_ADMIN.name()));
+
+    Plugin plugin = pluginService.getByHash(hash);
+    if (plugin == null) {
+      throw new ObjectNotFoundException("Plugin with given hash does not exist");
+    }
+    serializer.checkETag(oldETag, plugin);
+    data.saveToPlugin(plugin, isAdmin);
+    pluginService.update(plugin);
+
+    return serializer.prepareResponse(plugin);
+  }
+
+  @PreAuthorize("hasAnyAuthority('IS_ADMIN')")
+  @DeleteMapping(value = "/{hash}")
+  public void deletePlugin(
+      final @RequestHeader(name = "If-Match", required = false) String oldETag,
+      final @PathVariable(value = "hash") String hash)
+      throws QueryException, ObjectNotFoundException {
+
+    final Plugin plugin = pluginService.getByHash(hash);
+    if (plugin == null) {
+      throw new ObjectNotFoundException("Plugin with given id doesn't exist");
+    }
+    serializer.checkETag(oldETag, plugin);
+
+    pluginService.delete(plugin);
+  }
+
+  @GetMapping(value = "/")
+  public ResponseEntity<?> listPlugins(final Pageable pageable) {
+    final Map<PluginProperty, Object> pluginFilter = new HashMap<>();
+    pluginFilter.put(PluginProperty.DEFAULT, true);
+    final Page<Plugin> plugins = pluginService.getAll(pluginFilter, pageable);
+    return serializer.prepareResponse(plugins);
+  }
+}
\ No newline at end of file
diff --git a/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginDTO.java b/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginDTO.java
new file mode 100644
index 0000000000..89c1db3551
--- /dev/null
+++ b/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginDTO.java
@@ -0,0 +1,90 @@
+package lcsb.mapviewer.web.api.plugin;
+
+import lcsb.mapviewer.api.QueryException;
+import lcsb.mapviewer.model.plugin.Plugin;
+import lcsb.mapviewer.web.api.AbstractDTO;
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+public class NewPluginDTO extends AbstractDTO {
+
+  @NotBlank
+  @Size(max = 255)
+  private String hash;
+
+  @NotNull
+  private String name;
+
+  @NotNull
+  private String version;
+
+  private boolean isPublic;
+
+  private Boolean isDefault;
+
+  @NotNull
+  private String url;
+
+  public void saveToPlugin(final Plugin plugin, final boolean isAdmin) throws QueryException {
+    plugin.setHash(hash);
+    plugin.setPublic(isPublic);
+    if (isDefault != null && isAdmin) {
+      plugin.setDefault(isDefault);
+    }
+    plugin.setName(name);
+    plugin.setVersion(version);
+    if (!plugin.getUrls().contains(url)) {
+      plugin.addUrl(url);
+    }
+  }
+
+  public String getHash() {
+    return hash;
+  }
+
+  public void setHash(final String hash) {
+    this.hash = hash;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(final String name) {
+    this.name = name;
+  }
+
+  public String getVersion() {
+    return version;
+  }
+
+  public void setVersion(final String version) {
+    this.version = version;
+  }
+
+  public boolean isPublic() {
+    return isPublic;
+  }
+
+  public void setPublic(final boolean aPublic) {
+    isPublic = aPublic;
+  }
+
+  public Boolean getDefault() {
+    return isDefault;
+  }
+
+  public void setDefault(final Boolean aDefault) {
+    isDefault = aDefault;
+  }
+
+  public String getUrl() {
+    return url;
+  }
+
+  public void setUrl(final String url) {
+    this.url = url;
+  }
+}
diff --git a/web/src/test/java/lcsb/mapviewer/web/ControllerIntegrationTest.java b/web/src/test/java/lcsb/mapviewer/web/ControllerIntegrationTest.java
index d1426a44bb..4713155af4 100644
--- a/web/src/test/java/lcsb/mapviewer/web/ControllerIntegrationTest.java
+++ b/web/src/test/java/lcsb/mapviewer/web/ControllerIntegrationTest.java
@@ -909,6 +909,10 @@ public abstract class ControllerIntegrationTest extends TestUtils {
     return pathParameters(parameterWithName("projectId").description("project identifier"));
   }
 
+  protected PathParametersSnippet getPluginPathParameters() {
+    return pathParameters(parameterWithName("hash").description("plugin hash"));
+  }
+
   protected PathParametersSnippet getMapPathParameters() {
     return getProjectPathParameters().and(parameterWithName("mapId").description("map identifier"));
   }
diff --git a/web/src/test/java/lcsb/mapviewer/web/PluginControllerIntegrationTest.java b/web/src/test/java/lcsb/mapviewer/web/PluginControllerIntegrationTest.java
index cdc7060fe6..6d5f2947b8 100644
--- a/web/src/test/java/lcsb/mapviewer/web/PluginControllerIntegrationTest.java
+++ b/web/src/test/java/lcsb/mapviewer/web/PluginControllerIntegrationTest.java
@@ -414,7 +414,8 @@ public class PluginControllerIntegrationTest extends ControllerIntegrationTest {
     plugin.setName("starter-kit");
     plugin.setVersion("0.0.1");
     plugin.addUrl(PLUGIN_URL);
-    return pluginService.add(plugin);
+    pluginService.add(plugin);
+    return plugin;
   }
 
   @Test
diff --git a/web/src/test/java/lcsb/mapviewer/web/api/NewApiDocs.java b/web/src/test/java/lcsb/mapviewer/web/api/NewApiDocs.java
index c452810cdf..9903d19166 100644
--- a/web/src/test/java/lcsb/mapviewer/web/api/NewApiDocs.java
+++ b/web/src/test/java/lcsb/mapviewer/web/api/NewApiDocs.java
@@ -54,6 +54,10 @@ public class NewApiDocs {
     return new NewApiDocs().getProjectFields(prefix);
   }
 
+  public static List<FieldDescriptor> getPluginResponse(final String prefix) {
+    return new NewApiDocs().getPluginFields(prefix);
+  }
+
   public static List<FieldDescriptor> getLayerResponse(final String prefix) {
     return new NewApiDocs().getLayerFields(prefix);
   }
@@ -1029,6 +1033,30 @@ public class NewApiDocs {
             .optional());
   }
 
+  public List<FieldDescriptor> getPluginFields(final String prefix) {
+    return Arrays.asList(
+        fieldWithPath(prefix + "hash")
+            .description("plugin hash (identifier)")
+            .type(JsonFieldType.STRING),
+        fieldWithPath(prefix + "name")
+            .description("name")
+            .type(JsonFieldType.STRING),
+        fieldWithPath(prefix + "version")
+            .description("version")
+            .type(JsonFieldType.STRING),
+        fieldWithPath(prefix + "isPublic")
+            .description("is the plugin visible in the list of plugins")
+            .type(JsonFieldType.BOOLEAN),
+        fieldWithPath(prefix + "isDefault")
+            .description("is the plugin automatically loaded when opening map")
+            .type(JsonFieldType.BOOLEAN),
+        subsectionWithPath(prefix + "urls")
+            .description("list of urls where plugin is accessible")
+            .type(JsonFieldType.ARRAY)
+            .optional()
+    );
+  }
+
   private static <T extends Enum<T>> String getOptionsAsString(final Class<T> enumType) {
     final List<String> options = new ArrayList<>();
     for (final T c : enumType.getEnumConstants()) {
diff --git a/web/src/test/java/lcsb/mapviewer/web/api/plugin/NewPluginControllerTest.java b/web/src/test/java/lcsb/mapviewer/web/api/plugin/NewPluginControllerTest.java
new file mode 100644
index 0000000000..32757059b5
--- /dev/null
+++ b/web/src/test/java/lcsb/mapviewer/web/api/plugin/NewPluginControllerTest.java
@@ -0,0 +1,486 @@
+package lcsb.mapviewer.web.api.plugin;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lcsb.mapviewer.model.plugin.Plugin;
+import lcsb.mapviewer.services.interfaces.IPluginService;
+import lcsb.mapviewer.web.ControllerIntegrationTest;
+import lcsb.mapviewer.web.api.NewApiDocs;
+import lcsb.mapviewer.web.api.NewApiResponseSerializer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.web.servlet.RequestBuilder;
+
+import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
+import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
+import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ActiveProfiles("webCtdTestProfile")
+public class NewPluginControllerTest extends ControllerIntegrationTest {
+
+  @Autowired
+  private IPluginService pluginService;
+
+  @Autowired
+  private NewApiResponseSerializer newApiResponseSerializer;
+
+  private ObjectMapper objectMapper;
+
+  @Before
+  public void setUp() throws Exception {
+    objectMapper = newApiResponseSerializer.getObjectMapper();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+  }
+
+  @Test
+  public void testGetPlugin() throws Exception {
+    Plugin plugin = createPlugin();
+    pluginService.add(plugin);
+
+    final RequestBuilder request = get("/minerva/new_api/plugins/{hash}", plugin.getHash());
+
+    final MockHttpServletResponse response = mockMvc.perform(request)
+        .andExpect(status().isOk())
+        .andDo(document("new_api/plugins/get_by_hash",
+            getPluginPathParameters(),
+            responseFields(NewApiDocs.getPluginResponse(""))))
+        .andExpect(status().is2xxSuccessful())
+        .andReturn().getResponse();
+    logger.debug(response.getContentAsString());
+  }
+
+  private static Plugin createPlugin() {
+    Plugin plugin = new Plugin();
+    plugin.setHash("x");
+    plugin.setName("plugin");
+    plugin.setVersion("1.0.0");
+    return plugin;
+  }
+//
+//  @Test
+//  public void testGetProjectContainsInfoAboutMinervaNet() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", BUILT_IN_PROJECT)
+//        .session(session);
+//
+//    final MockHttpServletResponse response = mockMvc.perform(request)
+//        .andExpect(status().isOk())
+//        .andReturn().getResponse();
+//
+//    final Map<String, Object> object = objectMapper.readValue(response.getContentAsString(), new TypeReference<Map<String, Object>>() {
+//    });
+//
+//    assertTrue(object.containsKey("sharedInMinervaNet"));
+//
+//  }
+//
+//  @Test
+//  public void testGetNonExisting() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", "unknown")
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isNotFound());
+//  }
+//
+//  @Test
+//  public void testGetProjectWithoutPermission() throws Exception {
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", TEST_PROJECT);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isForbidden());
+//  }
+//
+//  @Test
+//  public void testCreateProject() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
+//
+//    final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .session(session);
+//
+//    final MockHttpServletResponse response = mockMvc.perform(request)
+//        .andExpect(status().isCreated())
+//        .andReturn().getResponse();
+//    assertNotNull(response.getHeader("ETag"));
+//
+//    assertNotNull(projectService.getProjectByProjectId(TEST_PROJECT));
+//  }
+//
+//  @Test
+//  public void testCreateProjectWithProjectIdMismatch() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
+//
+//    final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", TEST_PROJECT_2)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isBadRequest());
+//  }
+//
+//  @Test
+//  public void testCreateProjectExisting() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final NewProjectDTO data = createProjectDTO(BUILT_IN_PROJECT);
+//
+//    final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", BUILT_IN_PROJECT)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isConflict());
+//  }
+//
+//  @Test
+//  public void testUpdateProject() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
+//
+//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .session(session);
+//
+//    final HttpServletResponse response = mockMvc.perform(request)
+//        .andExpect(status().isOk())
+//        .andReturn().getResponse();
+//    assertNotNull(response.getHeader("ETag"));
+//
+//    final Project project = projectService.getProjectByProjectId(TEST_PROJECT);
+//    assertEquals(data.getVersion(), project.getVersion());
+//  }
+//
+//  @Test
+//  public void testUpdateProjectWithMissingData() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
+//    data.setName(null);
+//
+//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isBadRequest());
+//  }
+//
+//  @Test
+//  public void testUpdateProjectWithOldVersion() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
+//
+//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .header("If-Match", "-1")
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isPreconditionFailed());
+//  }
+//
+//  @Test
+//  public void testUpdateProjectWithGoodVersion() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    Project project = createAndPersistProject(TEST_PROJECT);
+//
+//    final String originalVersion = project.getEntityVersion() + "";
+//
+//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
+//
+//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .header("If-Match", originalVersion)
+//        .session(session);
+//
+//    final HttpServletResponse response = mockMvc.perform(request)
+//        .andExpect(status().isOk())
+//        .andReturn().getResponse();
+//    assertNotNull(response.getHeader("ETag"));
+//
+//    project = projectService.getProjectByProjectId(TEST_PROJECT);
+//    assertNotEquals(originalVersion, project.getEntityVersion() + "");
+//  }
+//
+//  private NewProjectDTO createProjectDTO(final String projectId) {
+//    final NewProjectDTO data = new NewProjectDTO();
+//    data.setName("my name");
+//    data.setNotifyEmail("minerva@uni.lu");
+//    data.setProjectId(projectId);
+//    data.setSbgnFormat(false);
+//    data.setVersion("0.0.1");
+//    return data;
+//  }
+//
+//  @Test
+//  public void testDeleteProject() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .session(session);
+//
+//    final String response = mockMvc.perform(request)
+//        .andExpect(status().isAccepted())
+//        .andReturn().getResponse()
+//        .getContentAsString();
+//
+//    final MinervaJob job = objectMapper.readValue(response, MinervaJob.class);
+//    assertEquals(MinervaJobType.DELETE_PROJECT, job.getJobType());
+//
+//    minervaJobService.waitForTasksToFinish();
+//
+//    assertNull(projectService.getProjectByProjectId(TEST_PROJECT));
+//  }
+//
+//  @Test
+//  public void testDeleteNotExistingProject() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isNotFound());
+//  }
+//
+//  @Test
+//  public void testDeleteNoAccessProject() throws Exception {
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isForbidden());
+//  }
+//
+//  @Test
+//  public void testDeleteDefaultProject() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", BUILT_IN_PROJECT)
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isForbidden());
+//  }
+//
+//  @Test
+//  public void testDeleteProjectDropsPrivileges() throws Exception {
+//    createAndPersistProject(TEST_PROJECT);
+//    User curator = createCurator(CURATOR_LOGIN, CURATOR_PASSWORD);
+//
+//    userService.grantUserPrivilege(curator, PrivilegeType.WRITE_PROJECT, TEST_PROJECT);
+//
+//    final RequestBuilder request = delete("/minerva/api/projects/" + TEST_PROJECT)
+//        .session(createSession(CURATOR_LOGIN, CURATOR_PASSWORD));
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().is2xxSuccessful());
+//
+//    minervaJobService.waitForTasksToFinish();
+//
+//    curator = userService.getUserByLogin(CURATOR_LOGIN, true);
+//
+//    assertFalse("Curator privileges weren't updated after project was removed",
+//        curator.getPrivileges().contains(new Privilege(PrivilegeType.WRITE_PROJECT, TEST_PROJECT)));
+//  }
+//
+//  @Test
+//  public void testDeleteProjectWithGoodVersion() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final Project project = createAndPersistProject(TEST_PROJECT);
+//
+//    final String originalVersion = project.getEntityVersion() + "";
+//
+//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .header("If-Match", originalVersion)
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isAccepted());
+//  }
+//
+//  @Test
+//  public void testDeleteProjectWithWrongVersion() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .header("If-Match", "-1")
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isPreconditionFailed());
+//  }
+//
+//  @Test
+//  public void testListProjects() throws Exception {
+//    final RequestBuilder request = get("/minerva/new_api/projects/");
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isOk());
+//  }
+//
+//  @Test
+//  public void testListProjectsWithoutAccess() throws Exception {
+//    final RequestBuilder request = get("/minerva/new_api/projects/");
+//
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final String response = mockMvc.perform(request)
+//        .andExpect(status().isOk())
+//        .andReturn().getResponse()
+//        .getContentAsString();
+//
+//    final Page<Object> page = objectMapper.readValue(response, new TypeReference<Page<Object>>() {
+//    });
+//    assertEquals(1, page.getTotalElements());
+//    assertEquals(1, page.getNumberOfElements());
+//  }
+//
+//  @Test
+//  public void testListProjectsWithoutAdminAccess() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//    final RequestBuilder request = get("/minerva/new_api/projects/")
+//        .session(session);
+//
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final String response = mockMvc.perform(request)
+//        .andExpect(status().isOk())
+//        .andReturn().getResponse()
+//        .getContentAsString();
+//
+//    final Page<Object> page = objectMapper.readValue(response, new TypeReference<Page<Object>>() {
+//    });
+//    assertEquals(2, page.getTotalElements());
+//    assertEquals(2, page.getNumberOfElements());
+//  }
+//
+//  @Test
+//  public void testUpdateOwnerInProject() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final NewUserLoginDTO data = new NewUserLoginDTO();
+//    data.setLogin(Configuration.ANONYMOUS_LOGIN);
+//
+//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/owner/", TEST_PROJECT)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .session(session);
+//
+//    final HttpServletResponse response = mockMvc.perform(request)
+//        .andExpect(status().isOk())
+//        .andReturn().getResponse();
+//    assertNotNull(response.getHeader("ETag"));
+//
+//    final Project project = projectService.getProjectByProjectId(TEST_PROJECT);
+//    assertEquals(Configuration.ANONYMOUS_LOGIN, project.getOwner().getLogin());
+//  }
+//
+//  @Test
+//  public void testUpdateInvalidOwnerInProject() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    createAndPersistProject(TEST_PROJECT);
+//
+//    final NewUserLoginDTO data = new NewUserLoginDTO();
+//    data.setLogin("blah");
+//
+//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/owner/", TEST_PROJECT)
+//        .contentType(MediaType.APPLICATION_JSON)
+//        .content(objectMapper.writeValueAsString(data))
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andExpect(status().isNotFound());
+//  }
+//
+//  @Test
+//  public void testGetProjects() throws Exception {
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final RequestBuilder request = get("/minerva/new_api/projects/")
+//        .session(session);
+//
+//    mockMvc.perform(request)
+//        .andDo(document("new_api/projects/get_projects",
+//            getProjectsSearchResult()))
+//        .andExpect(status().is2xxSuccessful())
+//        .andReturn().getResponse().getContentAsString();
+//  }
+//
+//  private Snippet getProjectsSearchResult() {
+//    final String prefix = "content[].";
+//    final List<FieldDescriptor> fields = new ArrayList<>();
+//    fields.add(
+//        fieldWithPath("content")
+//            .description("list of projects on the page")
+//            .type(JsonFieldType.ARRAY));
+//    fields.addAll(NewApiDocs.getProjectResponse(prefix));
+//    fields.addAll(NewApiDocs.getPageableFields());
+//    return responseFields(fields);
+//  }
+//
+//  @Test
+//  public void testGetProjectWithError() throws Exception {
+//    Project project = new Project(TEST_PROJECT);
+//    projectService.add(project);
+//
+//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+//
+//    final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
+//        .session(session);
+//
+//    final MockHttpServletResponse response = mockMvc.perform(request)
+//        .andExpect(status().isInternalServerError())
+//        .andReturn().getResponse();
+//
+//    Map<String, String> data = objectMapper.readValue(response.getContentAsString(), new TypeReference<Map<String, String>>() {
+//    });
+//
+//    assertNotNull(data.get("error-id"));
+//    assertNotNull(stacktraceService.getById(data.get("error-id")));
+//  }
+//
+}
-- 
GitLab


From 67bd0e75636c28546107ea6f3f171e8539c42eec Mon Sep 17 00:00:00 2001
From: Piotr Gawron <p.gawron@atcomp.pl>
Date: Fri, 17 Jan 2025 09:41:40 +0100
Subject: [PATCH 2/2] new api endpoints for plugins

---
 .../persist/dao/plugin/PluginDao.java         |  36 +-
 .../persist/dao/plugin/PluginProperty.java    |   2 +-
 .../api/plugins/PluginController.java         |  14 +-
 .../services/impl/PluginService.java          |  14 +-
 .../services/interfaces/IPluginService.java   |   1 +
 .../web/api/plugin/NewPluginController.java   |   6 +-
 .../web/api/plugin/NewPluginDTO.java          |  23 +-
 .../api/plugin/NewPluginControllerTest.java   | 680 +++++++-----------
 8 files changed, 325 insertions(+), 451 deletions(-)

diff --git a/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginDao.java b/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginDao.java
index 2ca006601a..0369e9f960 100644
--- a/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginDao.java
+++ b/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginDao.java
@@ -1,22 +1,25 @@
 package lcsb.mapviewer.persist.dao.plugin;
 
-import org.springframework.stereotype.Repository;
-
+import lcsb.mapviewer.common.exception.InvalidArgumentException;
 import lcsb.mapviewer.model.plugin.Plugin;
 import lcsb.mapviewer.persist.dao.BaseDao;
-import lcsb.mapviewer.persist.dao.MinervaEntityProperty;
+import org.springframework.stereotype.Repository;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Data access object class for {@link Plugin} objects.
- * 
+ *
  * @author Piotr Gawron
- * 
  */
 @Repository
-public class PluginDao extends BaseDao<Plugin, MinervaEntityProperty<Plugin>> {
-  /**
-   * Default constructor.
-   */
+public class PluginDao extends BaseDao<Plugin, PluginProperty> {
+
   public PluginDao() {
     super(Plugin.class);
   }
@@ -25,4 +28,19 @@ public class PluginDao extends BaseDao<Plugin, MinervaEntityProperty<Plugin>> {
     return getByParameter("hash", hash);
   }
 
+  @Override
+  protected Predicate createPredicate(final Map<PluginProperty, Object> filterOptions, final Root<Plugin> root) {
+    final CriteriaBuilder builder = getSession().getCriteriaBuilder();
+    final List<Predicate> predicates = new ArrayList<>();
+
+    for (final PluginProperty key : filterOptions.keySet()) {
+      if (key.equals(PluginProperty.DEFAULT_VALUE)) {
+        final Predicate predicate = builder.and(root.get("isDefault").in(filterOptions.get(key)));
+        predicates.add(predicate);
+      } else {
+        throw new InvalidArgumentException("Unknown property: " + key);
+      }
+    }
+    return builder.and(predicates.toArray(new Predicate[0]));
+  }
 }
diff --git a/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginProperty.java b/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginProperty.java
index 073fb582b8..79b1ba0c9d 100644
--- a/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginProperty.java
+++ b/persist/src/main/java/lcsb/mapviewer/persist/dao/plugin/PluginProperty.java
@@ -4,5 +4,5 @@ import lcsb.mapviewer.model.plugin.Plugin;
 import lcsb.mapviewer.persist.dao.MinervaEntityProperty;
 
 public enum PluginProperty implements MinervaEntityProperty<Plugin> {
-  DEFAULT,
+  DEFAULT_VALUE,
 }
diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/plugins/PluginController.java b/rest-api/src/main/java/lcsb/mapviewer/api/plugins/PluginController.java
index f12b1eb7ca..ec095be816 100644
--- a/rest-api/src/main/java/lcsb/mapviewer/api/plugins/PluginController.java
+++ b/rest-api/src/main/java/lcsb/mapviewer/api/plugins/PluginController.java
@@ -1,9 +1,7 @@
 package lcsb.mapviewer.api.plugins;
 
-import lcsb.mapviewer.annotation.cache.WebPageDownloader;
 import lcsb.mapviewer.api.BaseController;
 import lcsb.mapviewer.api.QueryException;
-import lcsb.mapviewer.common.Md5;
 import lcsb.mapviewer.model.plugin.Plugin;
 import lcsb.mapviewer.model.plugin.PluginDataEntry;
 import lcsb.mapviewer.model.user.User;
@@ -29,7 +27,6 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
-import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
@@ -82,14 +79,15 @@ public class PluginController extends BaseController {
       if (!url.isEmpty()) {
         plugin.addUrl(url);
         if (!url.startsWith("http://localhost")) {
+          String md5;
           try {
-            String md5 = Md5.compute(new WebPageDownloader().getFromNetwork(url));
-            if (!Objects.equals(md5, hash)) {
-              throw new QueryException(String.format("Invalid hash. Expected %s, but %s found", md5, hash));
-            }
-          } catch (IOException e) {
+            md5 = pluginService.getExpectedHash(url);
+          } catch (Exception e) {
             throw new QueryException("Problem with fetching url", e);
           }
+          if (!Objects.equals(md5, hash)) {
+            throw new QueryException(String.format("Invalid hash. Expected %s, but %s found", md5, hash));
+          }
         }
       }
       pluginService.add(plugin);
diff --git a/service/src/main/java/lcsb/mapviewer/services/impl/PluginService.java b/service/src/main/java/lcsb/mapviewer/services/impl/PluginService.java
index ed90d60d46..699e67d1ca 100644
--- a/service/src/main/java/lcsb/mapviewer/services/impl/PluginService.java
+++ b/service/src/main/java/lcsb/mapviewer/services/impl/PluginService.java
@@ -1,5 +1,7 @@
 package lcsb.mapviewer.services.impl;
 
+import lcsb.mapviewer.annotation.cache.WebPageDownloader;
+import lcsb.mapviewer.common.Md5;
 import lcsb.mapviewer.common.exception.NotImplementedException;
 import lcsb.mapviewer.model.plugin.Plugin;
 import lcsb.mapviewer.model.plugin.PluginDataEntry;
@@ -15,6 +17,7 @@ import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
@@ -51,7 +54,7 @@ public class PluginService implements IPluginService {
 
   @Override
   public Page<Plugin> getAll(final Map<PluginProperty, Object> filter, final Pageable pageable) {
-    throw new NotImplementedException();
+    return pluginDao.getAll(filter, pageable);
   }
 
 
@@ -105,6 +108,15 @@ public class PluginService implements IPluginService {
     pluginDao.update(plugin);
   }
 
+  @Override
+  public String getExpectedHash(final String url) {
+    try {
+      return Md5.compute(new WebPageDownloader().getFromNetwork(url));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
 
   @Override
   public Plugin getById(final int id) {
diff --git a/service/src/main/java/lcsb/mapviewer/services/interfaces/IPluginService.java b/service/src/main/java/lcsb/mapviewer/services/interfaces/IPluginService.java
index 9c98718c79..e71f4ef849 100644
--- a/service/src/main/java/lcsb/mapviewer/services/interfaces/IPluginService.java
+++ b/service/src/main/java/lcsb/mapviewer/services/interfaces/IPluginService.java
@@ -27,4 +27,5 @@ public interface IPluginService extends CrudService<Plugin, PluginProperty> {
 
   void update(final Plugin plugin);
 
+  String getExpectedHash(String url);
 }
diff --git a/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginController.java b/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginController.java
index 4d6558bd47..1803cbd3b9 100644
--- a/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginController.java
+++ b/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginController.java
@@ -80,7 +80,7 @@ public class NewPluginController {
       throw new ObjectExistsException("Plugin with given hash already exists");
     }
     plugin = new Plugin();
-    data.saveToPlugin(plugin, isAdmin);
+    data.saveToPlugin(plugin, isAdmin, pluginService::getExpectedHash);
     pluginService.add(plugin);
 
     return serializer.prepareResponse(plugin, HttpStatus.CREATED);
@@ -102,7 +102,7 @@ public class NewPluginController {
       throw new ObjectNotFoundException("Plugin with given hash does not exist");
     }
     serializer.checkETag(oldETag, plugin);
-    data.saveToPlugin(plugin, isAdmin);
+    data.saveToPlugin(plugin, isAdmin, pluginService::getExpectedHash);
     pluginService.update(plugin);
 
     return serializer.prepareResponse(plugin);
@@ -127,7 +127,7 @@ public class NewPluginController {
   @GetMapping(value = "/")
   public ResponseEntity<?> listPlugins(final Pageable pageable) {
     final Map<PluginProperty, Object> pluginFilter = new HashMap<>();
-    pluginFilter.put(PluginProperty.DEFAULT, true);
+    pluginFilter.put(PluginProperty.DEFAULT_VALUE, true);
     final Page<Plugin> plugins = pluginService.getAll(pluginFilter, pageable);
     return serializer.prepareResponse(plugins);
   }
diff --git a/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginDTO.java b/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginDTO.java
index 89c1db3551..35212a980f 100644
--- a/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginDTO.java
+++ b/web/src/main/java/lcsb/mapviewer/web/api/plugin/NewPluginDTO.java
@@ -7,6 +7,8 @@ import org.hibernate.validator.constraints.NotBlank;
 
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
+import java.util.Objects;
+import java.util.function.Function;
 
 public class NewPluginDTO extends AbstractDTO {
 
@@ -27,7 +29,7 @@ public class NewPluginDTO extends AbstractDTO {
   @NotNull
   private String url;
 
-  public void saveToPlugin(final Plugin plugin, final boolean isAdmin) throws QueryException {
+  public void saveToPlugin(final Plugin plugin, final boolean isAdmin, final Function<String, String> urlToHash) throws QueryException {
     plugin.setHash(hash);
     plugin.setPublic(isPublic);
     if (isDefault != null && isAdmin) {
@@ -36,6 +38,17 @@ public class NewPluginDTO extends AbstractDTO {
     plugin.setName(name);
     plugin.setVersion(version);
     if (!plugin.getUrls().contains(url)) {
+      if (!url.startsWith("http://localhost")) {
+        String md5;
+        try {
+          md5 = urlToHash.apply(url);
+        } catch (Exception e) {
+          throw new QueryException("Problem with fetching source file: " + url, e);
+        }
+        if (!Objects.equals(md5, hash)) {
+          throw new QueryException(String.format("Invalid hash. Expected %s, but %s found", md5, hash));
+        }
+      }
       plugin.addUrl(url);
     }
   }
@@ -68,16 +81,16 @@ public class NewPluginDTO extends AbstractDTO {
     return isPublic;
   }
 
-  public void setPublic(final boolean aPublic) {
-    isPublic = aPublic;
+  public void setPublic(final boolean isPublic) {
+    this.isPublic = isPublic;
   }
 
   public Boolean getDefault() {
     return isDefault;
   }
 
-  public void setDefault(final Boolean aDefault) {
-    isDefault = aDefault;
+  public void setDefault(final Boolean isDefault) {
+    this.isDefault = isDefault;
   }
 
   public String getUrl() {
diff --git a/web/src/test/java/lcsb/mapviewer/web/api/plugin/NewPluginControllerTest.java b/web/src/test/java/lcsb/mapviewer/web/api/plugin/NewPluginControllerTest.java
index 32757059b5..2fefda636c 100644
--- a/web/src/test/java/lcsb/mapviewer/web/api/plugin/NewPluginControllerTest.java
+++ b/web/src/test/java/lcsb/mapviewer/web/api/plugin/NewPluginControllerTest.java
@@ -11,13 +11,24 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
 import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.web.servlet.RequestBuilder;
 
+import javax.servlet.http.HttpServletResponse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
+import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
 import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
+import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
+import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put;
 import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
@@ -25,6 +36,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 @ActiveProfiles("webCtdTestProfile")
 public class NewPluginControllerTest extends ControllerIntegrationTest {
 
+  private static final String PLUGIN_URL = "https://minerva-dev.lcsb.uni.lu/plugins/starter-kit/plugin.js";
+
+  private static final String PLUGIN_HASH = "b7a875a0536949d4d8a2f834dc54489e";
+
   @Autowired
   private IPluginService pluginService;
 
@@ -40,447 +55,264 @@ public class NewPluginControllerTest extends ControllerIntegrationTest {
 
   @After
   public void tearDown() throws Exception {
+    Plugin plugin = pluginService.getByHash(PLUGIN_HASH);
+    if (plugin != null) {
+      pluginService.delete(plugin);
+    }
   }
 
   @Test
   public void testGetPlugin() throws Exception {
-    Plugin plugin = createPlugin();
+    Plugin plugin = createPlugin(PLUGIN_HASH);
     pluginService.add(plugin);
 
     final RequestBuilder request = get("/minerva/new_api/plugins/{hash}", plugin.getHash());
 
-    final MockHttpServletResponse response = mockMvc.perform(request)
+    mockMvc.perform(request)
         .andExpect(status().isOk())
         .andDo(document("new_api/plugins/get_by_hash",
             getPluginPathParameters(),
             responseFields(NewApiDocs.getPluginResponse(""))))
-        .andExpect(status().is2xxSuccessful())
-        .andReturn().getResponse();
-    logger.debug(response.getContentAsString());
+        .andExpect(status().is2xxSuccessful());
   }
 
-  private static Plugin createPlugin() {
+  private static Plugin createPlugin(final String hash) {
     Plugin plugin = new Plugin();
-    plugin.setHash("x");
+    plugin.setHash(hash);
     plugin.setName("plugin");
     plugin.setVersion("1.0.0");
     return plugin;
   }
-//
-//  @Test
-//  public void testGetProjectContainsInfoAboutMinervaNet() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", BUILT_IN_PROJECT)
-//        .session(session);
-//
-//    final MockHttpServletResponse response = mockMvc.perform(request)
-//        .andExpect(status().isOk())
-//        .andReturn().getResponse();
-//
-//    final Map<String, Object> object = objectMapper.readValue(response.getContentAsString(), new TypeReference<Map<String, Object>>() {
-//    });
-//
-//    assertTrue(object.containsKey("sharedInMinervaNet"));
-//
-//  }
-//
-//  @Test
-//  public void testGetNonExisting() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", "unknown")
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isNotFound());
-//  }
-//
-//  @Test
-//  public void testGetProjectWithoutPermission() throws Exception {
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", TEST_PROJECT);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isForbidden());
-//  }
-//
-//  @Test
-//  public void testCreateProject() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
-//
-//    final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .session(session);
-//
-//    final MockHttpServletResponse response = mockMvc.perform(request)
-//        .andExpect(status().isCreated())
-//        .andReturn().getResponse();
-//    assertNotNull(response.getHeader("ETag"));
-//
-//    assertNotNull(projectService.getProjectByProjectId(TEST_PROJECT));
-//  }
-//
-//  @Test
-//  public void testCreateProjectWithProjectIdMismatch() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
-//
-//    final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", TEST_PROJECT_2)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isBadRequest());
-//  }
-//
-//  @Test
-//  public void testCreateProjectExisting() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final NewProjectDTO data = createProjectDTO(BUILT_IN_PROJECT);
-//
-//    final RequestBuilder request = post("/minerva/new_api/projects/{projectId}/", BUILT_IN_PROJECT)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isConflict());
-//  }
-//
-//  @Test
-//  public void testUpdateProject() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
-//
-//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .session(session);
-//
-//    final HttpServletResponse response = mockMvc.perform(request)
-//        .andExpect(status().isOk())
-//        .andReturn().getResponse();
-//    assertNotNull(response.getHeader("ETag"));
-//
-//    final Project project = projectService.getProjectByProjectId(TEST_PROJECT);
-//    assertEquals(data.getVersion(), project.getVersion());
-//  }
-//
-//  @Test
-//  public void testUpdateProjectWithMissingData() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
-//    data.setName(null);
-//
-//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isBadRequest());
-//  }
-//
-//  @Test
-//  public void testUpdateProjectWithOldVersion() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
-//
-//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .header("If-Match", "-1")
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isPreconditionFailed());
-//  }
-//
-//  @Test
-//  public void testUpdateProjectWithGoodVersion() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    Project project = createAndPersistProject(TEST_PROJECT);
-//
-//    final String originalVersion = project.getEntityVersion() + "";
-//
-//    final NewProjectDTO data = createProjectDTO(TEST_PROJECT);
-//
-//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .header("If-Match", originalVersion)
-//        .session(session);
-//
-//    final HttpServletResponse response = mockMvc.perform(request)
-//        .andExpect(status().isOk())
-//        .andReturn().getResponse();
-//    assertNotNull(response.getHeader("ETag"));
-//
-//    project = projectService.getProjectByProjectId(TEST_PROJECT);
-//    assertNotEquals(originalVersion, project.getEntityVersion() + "");
-//  }
-//
-//  private NewProjectDTO createProjectDTO(final String projectId) {
-//    final NewProjectDTO data = new NewProjectDTO();
-//    data.setName("my name");
-//    data.setNotifyEmail("minerva@uni.lu");
-//    data.setProjectId(projectId);
-//    data.setSbgnFormat(false);
-//    data.setVersion("0.0.1");
-//    return data;
-//  }
-//
-//  @Test
-//  public void testDeleteProject() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .session(session);
-//
-//    final String response = mockMvc.perform(request)
-//        .andExpect(status().isAccepted())
-//        .andReturn().getResponse()
-//        .getContentAsString();
-//
-//    final MinervaJob job = objectMapper.readValue(response, MinervaJob.class);
-//    assertEquals(MinervaJobType.DELETE_PROJECT, job.getJobType());
-//
-//    minervaJobService.waitForTasksToFinish();
-//
-//    assertNull(projectService.getProjectByProjectId(TEST_PROJECT));
-//  }
-//
-//  @Test
-//  public void testDeleteNotExistingProject() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isNotFound());
-//  }
-//
-//  @Test
-//  public void testDeleteNoAccessProject() throws Exception {
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isForbidden());
-//  }
-//
-//  @Test
-//  public void testDeleteDefaultProject() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", BUILT_IN_PROJECT)
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isForbidden());
-//  }
-//
-//  @Test
-//  public void testDeleteProjectDropsPrivileges() throws Exception {
-//    createAndPersistProject(TEST_PROJECT);
-//    User curator = createCurator(CURATOR_LOGIN, CURATOR_PASSWORD);
-//
-//    userService.grantUserPrivilege(curator, PrivilegeType.WRITE_PROJECT, TEST_PROJECT);
-//
-//    final RequestBuilder request = delete("/minerva/api/projects/" + TEST_PROJECT)
-//        .session(createSession(CURATOR_LOGIN, CURATOR_PASSWORD));
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().is2xxSuccessful());
-//
-//    minervaJobService.waitForTasksToFinish();
-//
-//    curator = userService.getUserByLogin(CURATOR_LOGIN, true);
-//
-//    assertFalse("Curator privileges weren't updated after project was removed",
-//        curator.getPrivileges().contains(new Privilege(PrivilegeType.WRITE_PROJECT, TEST_PROJECT)));
-//  }
-//
-//  @Test
-//  public void testDeleteProjectWithGoodVersion() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final Project project = createAndPersistProject(TEST_PROJECT);
-//
-//    final String originalVersion = project.getEntityVersion() + "";
-//
-//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .header("If-Match", originalVersion)
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isAccepted());
-//  }
-//
-//  @Test
-//  public void testDeleteProjectWithWrongVersion() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final RequestBuilder request = delete("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .header("If-Match", "-1")
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isPreconditionFailed());
-//  }
-//
-//  @Test
-//  public void testListProjects() throws Exception {
-//    final RequestBuilder request = get("/minerva/new_api/projects/");
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isOk());
-//  }
-//
-//  @Test
-//  public void testListProjectsWithoutAccess() throws Exception {
-//    final RequestBuilder request = get("/minerva/new_api/projects/");
-//
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final String response = mockMvc.perform(request)
-//        .andExpect(status().isOk())
-//        .andReturn().getResponse()
-//        .getContentAsString();
-//
-//    final Page<Object> page = objectMapper.readValue(response, new TypeReference<Page<Object>>() {
-//    });
-//    assertEquals(1, page.getTotalElements());
-//    assertEquals(1, page.getNumberOfElements());
-//  }
-//
-//  @Test
-//  public void testListProjectsWithoutAdminAccess() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//    final RequestBuilder request = get("/minerva/new_api/projects/")
-//        .session(session);
-//
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final String response = mockMvc.perform(request)
-//        .andExpect(status().isOk())
-//        .andReturn().getResponse()
-//        .getContentAsString();
-//
-//    final Page<Object> page = objectMapper.readValue(response, new TypeReference<Page<Object>>() {
-//    });
-//    assertEquals(2, page.getTotalElements());
-//    assertEquals(2, page.getNumberOfElements());
-//  }
-//
-//  @Test
-//  public void testUpdateOwnerInProject() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final NewUserLoginDTO data = new NewUserLoginDTO();
-//    data.setLogin(Configuration.ANONYMOUS_LOGIN);
-//
-//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/owner/", TEST_PROJECT)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .session(session);
-//
-//    final HttpServletResponse response = mockMvc.perform(request)
-//        .andExpect(status().isOk())
-//        .andReturn().getResponse();
-//    assertNotNull(response.getHeader("ETag"));
-//
-//    final Project project = projectService.getProjectByProjectId(TEST_PROJECT);
-//    assertEquals(Configuration.ANONYMOUS_LOGIN, project.getOwner().getLogin());
-//  }
-//
-//  @Test
-//  public void testUpdateInvalidOwnerInProject() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    createAndPersistProject(TEST_PROJECT);
-//
-//    final NewUserLoginDTO data = new NewUserLoginDTO();
-//    data.setLogin("blah");
-//
-//    final RequestBuilder request = put("/minerva/new_api/projects/{projectId}/owner/", TEST_PROJECT)
-//        .contentType(MediaType.APPLICATION_JSON)
-//        .content(objectMapper.writeValueAsString(data))
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andExpect(status().isNotFound());
-//  }
-//
-//  @Test
-//  public void testGetProjects() throws Exception {
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final RequestBuilder request = get("/minerva/new_api/projects/")
-//        .session(session);
-//
-//    mockMvc.perform(request)
-//        .andDo(document("new_api/projects/get_projects",
-//            getProjectsSearchResult()))
-//        .andExpect(status().is2xxSuccessful())
-//        .andReturn().getResponse().getContentAsString();
-//  }
-//
-//  private Snippet getProjectsSearchResult() {
-//    final String prefix = "content[].";
-//    final List<FieldDescriptor> fields = new ArrayList<>();
-//    fields.add(
-//        fieldWithPath("content")
-//            .description("list of projects on the page")
-//            .type(JsonFieldType.ARRAY));
-//    fields.addAll(NewApiDocs.getProjectResponse(prefix));
-//    fields.addAll(NewApiDocs.getPageableFields());
-//    return responseFields(fields);
-//  }
-//
-//  @Test
-//  public void testGetProjectWithError() throws Exception {
-//    Project project = new Project(TEST_PROJECT);
-//    projectService.add(project);
-//
-//    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
-//
-//    final RequestBuilder request = get("/minerva/new_api/projects/{projectId}/", TEST_PROJECT)
-//        .session(session);
-//
-//    final MockHttpServletResponse response = mockMvc.perform(request)
-//        .andExpect(status().isInternalServerError())
-//        .andReturn().getResponse();
-//
-//    Map<String, String> data = objectMapper.readValue(response.getContentAsString(), new TypeReference<Map<String, String>>() {
-//    });
-//
-//    assertNotNull(data.get("error-id"));
-//    assertNotNull(stacktraceService.getById(data.get("error-id")));
-//  }
-//
+
+  @Test
+  public void testGetNonExisting() throws Exception {
+    final RequestBuilder request = get("/minerva/new_api/plugins/{pluginId}/", "unknown");
+
+    mockMvc.perform(request)
+        .andExpect(status().isNotFound());
+  }
+
+  @Test
+  public void testCreatePlugin() throws Exception {
+    final NewPluginDTO data = createPluginDTO(PLUGIN_HASH);
+
+    final RequestBuilder request = post("/minerva/new_api/plugins/")
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(data));
+
+    final MockHttpServletResponse response = mockMvc.perform(request)
+        .andExpect(status().isCreated())
+        .andReturn().getResponse();
+    assertNotNull(response.getHeader("ETag"));
+
+    assertNotNull(pluginService.getByHash(PLUGIN_HASH));
+  }
+
+  @Test
+  public void testCreatePluginWithHashMismatch() throws Exception {
+    final NewPluginDTO data = createPluginDTO(TEST_PROJECT);
+
+    final RequestBuilder request = post("/minerva/new_api/plugins/")
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(data));
+
+    mockMvc.perform(request)
+        .andExpect(status().isBadRequest());
+  }
+
+  @Test
+  public void testCreatePluginExisting() throws Exception {
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    final NewPluginDTO data = createPluginDTO(PLUGIN_HASH);
+
+    final RequestBuilder request = post("/minerva/new_api/plugins/")
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(data));
+
+    mockMvc.perform(request)
+        .andExpect(status().isConflict());
+  }
+
+  @Test
+  public void testUpdatePlugin() throws Exception {
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    final NewPluginDTO data = createPluginDTO(PLUGIN_HASH);
+
+    final RequestBuilder request = put("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH)
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(data))
+        .session(session);
+
+    final HttpServletResponse response = mockMvc.perform(request)
+        .andExpect(status().isOk())
+        .andReturn().getResponse();
+    assertNotNull(response.getHeader("ETag"));
+
+    plugin = pluginService.getByHash(PLUGIN_HASH);
+    assertEquals(data.getVersion(), plugin.getVersion());
+  }
+
+
+  @Test
+  public void testUpdatePluginWithMissingData() throws Exception {
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    final NewPluginDTO data = createPluginDTO(PLUGIN_HASH);
+    data.setName(null);
+
+    final RequestBuilder request = put("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH)
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(data))
+        .session(session);
+
+    mockMvc.perform(request)
+        .andExpect(status().isBadRequest());
+  }
+
+  @Test
+  public void testUpdatePluginWithOldVersion() throws Exception {
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    final NewPluginDTO data = createPluginDTO(PLUGIN_HASH);
+
+    final RequestBuilder request = put("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH)
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(data))
+        .header("If-Match", "-1")
+        .session(session);
+
+    mockMvc.perform(request)
+        .andExpect(status().isPreconditionFailed());
+  }
+
+  @Test
+  public void testUpdatePluginWithGoodVersion() throws Exception {
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    String originalVersion = plugin.getEntityVersion() + "";
+
+    final NewPluginDTO data = createPluginDTO(PLUGIN_HASH);
+
+    final RequestBuilder request = put("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH)
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(data))
+        .header("If-Match", originalVersion)
+        .session(session);
+
+    final HttpServletResponse response = mockMvc.perform(request)
+        .andExpect(status().isOk())
+        .andReturn().getResponse();
+    assertNotNull(response.getHeader("ETag"));
+
+    plugin = pluginService.getByHash(PLUGIN_HASH);
+    assertEquals(data.getVersion(), plugin.getVersion());
+
+    assertNotEquals(originalVersion, plugin.getEntityVersion() + "");
+  }
+
+  private NewPluginDTO createPluginDTO(final String hash) {
+    final NewPluginDTO data = new NewPluginDTO();
+    data.setName("my name");
+    data.setHash(hash);
+    data.setName("starter-kit");
+    data.setVersion("0.0.1");
+    data.setUrl(PLUGIN_URL);
+    return data;
+  }
+
+
+  @Test
+  public void testDeletePlugin() throws Exception {
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    final RequestBuilder request = delete("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH)
+        .session(session);
+
+    mockMvc.perform(request)
+        .andExpect(status().isOk());
+
+    assertNull(pluginService.getByHash(PLUGIN_HASH));
+  }
+
+
+  @Test
+  public void testDeleteNotExistingPlugin() throws Exception {
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+    final RequestBuilder request = delete("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH)
+        .session(session);
+
+    mockMvc.perform(request)
+        .andExpect(status().isNotFound());
+  }
+
+  @Test
+  public void testDeleteNoAccessPlugin() throws Exception {
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    final RequestBuilder request = delete("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH);
+
+    mockMvc.perform(request)
+        .andExpect(status().isForbidden());
+  }
+
+  @Test
+  public void testDeletePluginWithGoodVersion() throws Exception {
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    final String originalVersion = plugin.getEntityVersion() + "";
+
+    final RequestBuilder request = delete("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH)
+        .header("If-Match", originalVersion)
+        .session(session);
+
+    mockMvc.perform(request)
+        .andExpect(status().isOk());
+  }
+
+  @Test
+  public void testDeletePluginWithWrongVersion() throws Exception {
+    final MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
+
+    Plugin plugin = createPlugin(PLUGIN_HASH);
+    pluginService.add(plugin);
+
+    final RequestBuilder request = delete("/minerva/new_api/plugins/{pluginId}/", PLUGIN_HASH)
+        .header("If-Match", "-1")
+        .session(session);
+
+    mockMvc.perform(request)
+        .andExpect(status().isPreconditionFailed());
+  }
+
+  @Test
+  public void testListPlugins() throws Exception {
+    final RequestBuilder request = get("/minerva/new_api/plugins/");
+
+    mockMvc.perform(request)
+        .andExpect(status().isOk());
+  }
 }
-- 
GitLab