Fix unique constraint on role. Added tests.

Change-Id: Iea36c28ed63b744103492396a71b057f93a3f882
diff --git a/src/main/java/de/ids_mannheim/korap/entity/Role.java b/src/main/java/de/ids_mannheim/korap/entity/Role.java
index da0a1e4..6e78582 100644
--- a/src/main/java/de/ids_mannheim/korap/entity/Role.java
+++ b/src/main/java/de/ids_mannheim/korap/entity/Role.java
@@ -40,7 +40,7 @@
     @Enumerated(EnumType.STRING)
     private PrivilegeType privilege;
     
-    @ManyToOne(fetch = FetchType.LAZY)
+    @ManyToOne(fetch = FetchType.EAGER)
     @JoinColumn(name = "query_id", referencedColumnName = "id")
     private QueryDO query;
 
@@ -100,8 +100,9 @@
     @Override
     public boolean equals (Object obj) {
         Role r = (Role) obj;
-        if (this.id == r.getId() && this.name.equals(r.getName())
-                && this.privilege.equals(r.getPrivilege())) {
+        if (this.name.equals(r.getName())
+                && this.privilege.equals(r.getPrivilege())
+                && this.userGroup.equals(r.getUserGroup())) {
             return true;
         }
         return false;
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index 94778c6..4638191 100644
--- a/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -81,16 +81,18 @@
     // policy errors
 
     // database codes
-    public static final int DB_GET_FAILED = 500;
+//    public static final int DB_GET_FAILED = 500;
     public static final int DB_INSERT_FAILED = 501;
-    public static final int DB_DELETE_FAILED = 502;
-    public static final int DB_UPDATE_FAILED = 503;
+//    public static final int DB_DELETE_FAILED = 502;
+//    public static final int DB_UPDATE_FAILED = 503;
 
-    public static final int DB_GET_SUCCESSFUL = 504;
-    public static final int DB_INSERT_SUCCESSFUL = 505;
-    public static final int DB_DELETE_SUCCESSFUL = 506;
-    public static final int DB_UPDATE_SUCCESSFUL = 507;
-    public static final int DB_ENTRY_EXISTS = 508;
+//    public static final int DB_GET_SUCCESSFUL = 504;
+//    public static final int DB_INSERT_SUCCESSFUL = 505;
+//    public static final int DB_DELETE_SUCCESSFUL = 506;
+//    public static final int DB_UPDATE_SUCCESSFUL = 507;
+//    public static final int DB_ENTRY_EXISTS = 508;
+    
+    public static final int DB_UNIQUE_CONSTRAINT_FAILED = 509;
 
     //    public static final int ARGUMENT_VALIDATION_FAILURE = 700;
     // public static final int ARGUMENT_VALIDATION_FAILURE = 701;
diff --git a/src/main/java/de/ids_mannheim/korap/service/QueryService.java b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
index b9d7c3c..c04441f 100644
--- a/src/main/java/de/ids_mannheim/korap/service/QueryService.java
+++ b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
@@ -97,7 +97,7 @@
     @Autowired
     private QueryConverter converter;
     @Autowired
-    private RoleConverter accessConverter;
+    private RoleConverter roleConverter;
 
     private void verifyUsername (String contextUsername, String pathUsername)
             throws KustvaktException {
@@ -500,13 +500,14 @@
                 Throwable lastCause = null;
                 while ((cause = cause.getCause()) != null
                         && !cause.equals(lastCause)) {
-                    if (cause instanceof SQLException) {
-                        break;
-                    }
+//                    if (cause instanceof SQLException) {
+//                        break;
+//                    }
                     lastCause = cause;
                 }
-                throw new KustvaktException(StatusCodes.DB_INSERT_FAILED,
-                        e.getMessage());
+                throw new KustvaktException(
+                        StatusCodes.DB_UNIQUE_CONSTRAINT_FAILED,
+                        lastCause.getMessage());
             }
 
             ResourceType queryType = query.getType();
@@ -592,7 +593,7 @@
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
         }
-        return accessConverter.createRoleDto(roles);
+        return roleConverter.createRoleDto(roles);
     }
 
     @Deprecated
diff --git a/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java b/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
index 6a4fa8b..a361716 100644
--- a/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
+++ b/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
@@ -649,31 +649,45 @@
                         memberUsername, member.getStatus().name());
             }
 
-            Set<Role> roles = member.getRoles();
+            Set<Role> existingRoles = member.getRoles();
             for (PredefinedRole role : roleNames) {
-                if (role.equals(PredefinedRole.GROUP_ADMIN)) {
-                    Role r1 = new Role(role,PrivilegeType.READ_MEMBER, userGroup);
-                    roleDao.addRole(r1);
-                    roles.add(r1);
-                    
-                    Role r2 = new Role(role,PrivilegeType.DELETE_MEMBER, userGroup);
-                    roleDao.addRole(r2);
-                    roles.add(r2);
+                boolean roleExists = false;
+                for (Role r :existingRoles) {
+                    if (r.getName().equals(role)) {
+                        roleExists = true;
+                        break;
+                    }
+                }
+                if (!roleExists) {
+                    if (role.equals(PredefinedRole.GROUP_ADMIN)) {
+                        Role r1 = new Role(role,PrivilegeType.READ_MEMBER, userGroup);
+                        roleDao.addRole(r1);
+                        existingRoles.add(r1);
+                        
+                        Role r2 = new Role(role,PrivilegeType.DELETE_MEMBER, userGroup);
+                        roleDao.addRole(r2);
+                        existingRoles.add(r2);
 
-                    Role r3 = new Role(role,PrivilegeType.WRITE_MEMBER, userGroup);
-                    roleDao.addRole(r3);
-                    roles.add(r3);
-                    
-                    Role r4 = new Role(role,PrivilegeType.SHARE_QUERY, userGroup);
-                    roleDao.addRole(r4);
-                    roles.add(r4);
-                    
-                    Role r5 = new Role(role,PrivilegeType.DELETE_QUERY, userGroup);
-                    roleDao.addRole(r5);
-                    roles.add(r5);
+                        Role r3 = new Role(role,PrivilegeType.WRITE_MEMBER, userGroup);
+                        roleDao.addRole(r3);
+                        existingRoles.add(r3);
+                        
+                        Role r4 = new Role(role,PrivilegeType.SHARE_QUERY, userGroup);
+                        roleDao.addRole(r4);
+                        existingRoles.add(r4);
+                        
+                        Role r5 = new Role(role,PrivilegeType.DELETE_QUERY, userGroup);
+                        roleDao.addRole(r5);
+                        existingRoles.add(r5);
+                    }
+//                    else {
+//                        throw new KustvaktException(StatusCodes.NOT_ALLOWED,
+//                                "Adding role " + role.name()
+//                                        + " is not allowed.");
+//                    }
                 }
             }
-            member.setRoles(roles);
+            member.setRoles(existingRoles);
             groupMemberDao.updateMember(member);
 
         }
@@ -682,6 +696,11 @@
                     "Unauthorized operation for user: " + username, username);
         }
     }
+    
+    private void checkRole () {
+        // TODO Auto-generated method stub
+
+    }
 
     public void deleteMemberRoles (String username, String groupName,
             String memberUsername, List<PredefinedRole> rolesToBeDeleted)
diff --git a/src/main/java/de/ids_mannheim/korap/web/KustvaktResponseHandler.java b/src/main/java/de/ids_mannheim/korap/web/KustvaktResponseHandler.java
index 1118b26..ba8a81e 100644
--- a/src/main/java/de/ids_mannheim/korap/web/KustvaktResponseHandler.java
+++ b/src/main/java/de/ids_mannheim/korap/web/KustvaktResponseHandler.java
@@ -28,6 +28,10 @@
             r = Response.status(Response.Status.BAD_REQUEST)
                     .entity(e.getNotification()).build();
         }
+        else if (e.getStatusCode() == StatusCodes.DB_UNIQUE_CONSTRAINT_FAILED) {
+            r = Response.status(Response.Status.CONFLICT)
+                    .entity(e.getNotification()).build();
+        }
         else if (e.getStatusCode() == StatusCodes.USER_REAUTHENTICATION_REQUIRED
                 || e.getStatusCode() == StatusCodes.AUTHORIZATION_FAILED
                 || e.getStatusCode() >= StatusCodes.AUTHENTICATION_FAILED) {
diff --git a/src/main/resources/db/sqlite/V1.13__user_group_alteration.sql b/src/main/resources/db/sqlite/V1.13__user_group_alteration.sql
index 6746d49..e167bde 100644
--- a/src/main/resources/db/sqlite/V1.13__user_group_alteration.sql
+++ b/src/main/resources/db/sqlite/V1.13__user_group_alteration.sql
@@ -1,7 +1,7 @@
 --DROP INDEX IF EXISTS group_member_role_index;
 --DROP INDEX IF EXISTS user_group_member_index;
 --DROP INDEX IF EXISTS user_group_member_status_index;
---DROP INDEX IF EXISTS role_index;
+DROP INDEX IF EXISTS role_index;
 
 -- please commented out the triggers in V1.2__triggers.sql later
 --DROP TRIGGER IF EXISTS insert_member_status;
@@ -59,6 +59,7 @@
 ALTER TABLE role_new RENAME TO role;
 
 DROP TABLE privilege;
---DROP TABLE group_member_role;
---DROP TABLE query_access;
---DROP TABLE user_group_member;
\ No newline at end of file
+DROP TABLE query_access;
+
+CREATE UNIQUE INDEX IF NOT EXISTS role_index on role(name, 
+  privilege, group_id, query_id);
\ No newline at end of file
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupListTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupListTest.java
index f418261..2da127c 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupListTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupListTest.java
@@ -2,22 +2,14 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import java.util.Set;
-
 import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
 
-import de.ids_mannheim.korap.constant.PredefinedRole;
-import de.ids_mannheim.korap.dao.UserGroupMemberDao;
-import de.ids_mannheim.korap.entity.Role;
-import de.ids_mannheim.korap.entity.UserGroupMember;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.utils.JsonUtils;
-import jakarta.ws.rs.core.Form;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.Response.Status;
 
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupMemberTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupMemberTest.java
index a3b3a12..6d81711 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupMemberTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupMemberTest.java
@@ -127,16 +127,17 @@
         subscribe(marlinGroupName, "dory");
         JsonNode marlinGroup = listUserGroups("marlin");
         int groupId = marlinGroup.at("/0/id").asInt();
-
-        Form form = new Form();
-        form.param("memberUsername", "dory");
-        form.param("role", PredefinedRole.GROUP_ADMIN.name());
-        addMemberRole(marlinGroupName, "marlin", form);
-
+        
         UserGroupMember member = memberDao.retrieveMemberById("dory", groupId);
         Set<Role> roles = member.getRoles();
+        assertEquals(1, roles.size());
+        
+        addAdminRole(marlinGroupName, "dory", "marlin");
+        
+        member = memberDao.retrieveMemberById("dory", groupId);
+        roles = member.getRoles();
         assertEquals(6, roles.size());
-
+        
         testAddSameMemberRole(groupId);
         testDeleteMemberRole(groupId);
         testEditMemberRoleEmpty(groupId);
@@ -145,14 +146,9 @@
         deleteGroupByName(marlinGroupName, "marlin");
     }
 
-    // EM: not work as expected since role is new.
     private void testAddSameMemberRole (int groupId)
             throws ProcessingException, KustvaktException {
-        Form form = new Form();
-        form.param("memberUsername", "dory");
-        form.param("role", PredefinedRole.GROUP_MEMBER.name());
-
-        addMemberRole(marlinGroupName, "marlin", form);
+        addAdminRole(marlinGroupName, "dory", "marlin");
 
         UserGroupMember member = memberDao.retrieveMemberById("dory", groupId);
         Set<Role> roles = member.getRoles();
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusAccessTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusAccessTest.java
index b2f7637..8eca508 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusAccessTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusAccessTest.java
@@ -21,109 +21,7 @@
 
     private String testUser = "VirtualCorpusAccessTest";
 
-    @Test
-    public void testlistAccessUnauthorized () throws KustvaktException {
-        createDoryGroup();
-        JsonNode node = listRolesByGroup("nemo", "dory-group");
-        assertEquals(StatusCodes.AUTHORIZATION_FAILED,
-                node.at("/errors/0/0").asInt());
-        assertEquals(node.at("/errors/0/1").asText(),
-                "Unauthorized operation for user: nemo");
-        deleteGroupByName(doryGroupName, "dory");
-    }
 
-    @Test
-    public void testDeleteSharedVC () throws KustvaktException {
-        createDoryGroup();
-
-        String vcName = "new_project_vc";
-        String username = "dory";
-        createProjectVC(username, vcName);
-
-        String groupName = "dory-group";
-        shareVCByCreator(username, vcName, groupName);
-
-        JsonNode node = listRolesByGroup(username, groupName);
-        assertEquals(1, node.size());
-//      System.out.println(node.toPrettyString());
-      //        assertEquals(2, node.at("/0/queryId").asInt());
-      assertEquals(node.at("/0/queryName").asText(), vcName);
-      //        assertEquals(2, node.at("/0/userGroupId").asInt());
-      assertEquals(node.at("/0/userGroupName").asText(), groupName);
-
-        // delete project VC
-        deleteVC(vcName, username, username);
-        node = listRolesByGroup(username, groupName);
-        assertEquals(0, node.size());
-
-        deleteGroupByName(doryGroupName, "dory");
-    }
-
-    @Test
-    public void testCreateDeleteAccess ()
-            throws ProcessingException, KustvaktException {
-        createMarlinGroup();
-
-        String vcName = "marlin-vc";
-        String groupName = "marlin-group";
-        // check the vc type
-        JsonNode node = retrieveVCInfo("marlin", "marlin", vcName);
-        assertEquals(vcName, node.at("/name").asText());
-        assertEquals(node.at("/type").asText(), "private");
-        // share vc to group
-        Response response = shareVCByCreator("marlin", vcName, groupName);
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        // check the vc type
-        node = retrieveVCInfo("marlin", "marlin", vcName);
-        assertEquals(node.at("/type").asText(), "project");
-        // list vc access by marlin
-        node = listRolesByGroup("marlin", groupName);
-        assertEquals(1, node.size());
-
-        // get access id
-        node = node.get(0);
-        assertEquals(5, node.at("/queryId").asInt());
-        assertEquals(vcName, node.at("/queryName").asText());
-        //        assertEquals(1, node.at("/userGroupId").asInt());
-        assertEquals(groupName, node.at("/userGroupName").asText());
-        assertEquals(1, node.at("/members").size());
-
-        String roleId = node.at("/roleId").asText();
-        // EM: TODO
-        //        testShareVC_nonUniqueAccess("marlin", vcName, groupName);
-
-        // delete unauthorized
-        response = deleteAccess(testUser, roleId);
-        testResponseUnauthorized(response, testUser);
-
-        testDeleteAccessByAdmin(roleId, groupName);
-
-        // edit VC back to private
-        String json = "{\"type\": \"" + ResourceType.PRIVATE + "\"}";
-        editVC("marlin", "marlin", vcName, json);
-        node = retrieveVCInfo("marlin", "marlin", vcName);
-        assertEquals(ResourceType.PRIVATE.displayName(),
-                node.at("/type").asText());
-
-        deleteGroupByName(marlinGroupName, "marlin");
-    }
-
-    private void testDeleteAccessByAdmin (String roleId, String groupName)
-            throws KustvaktException {
-        inviteMember(marlinGroupName, "marlin", "nemo");
-        subscribe(marlinGroupName, "nemo");
-
-        Form form = new Form();
-        form.param("memberUsername", "nemo");
-        form.param("role", PredefinedRole.GROUP_ADMIN.name());
-        addMemberRole(marlinGroupName, "marlin", form);
-
-        Response response = deleteAccess("nemo", roleId);
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-
-        JsonNode node = listRolesByGroup("nemo", groupName);
-        assertEquals(0, node.size());
-    }
 
     private void testShareVC_nonUniqueAccess (String vcCreator, String vcName,
             String groupName) throws ProcessingException, KustvaktException {
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
index 25ad065..13ce875 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
@@ -41,6 +41,25 @@
     }
 
     @Test
+    public void testDeleteVC_unauthorized () throws KustvaktException {
+        Response response = target().path(API_VERSION).path("vc").path("~dory")
+                .path("dory-vc").request()
+                .header(Attributes.AUTHORIZATION, authHeader).delete();
+        testResponseUnauthorized(response, testUser);
+    }
+    
+    private void testDeleteSystemVC (String vcName) throws KustvaktException {
+        Response response = deleteVC(vcName, "system", "admin");
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    private void testDeleteSystemVC_unauthorized (String vcName,
+            String username) throws KustvaktException {
+        Response response = deleteVC(vcName, "system", username);
+        testResponseUnauthorized(response, "dory");
+    }
+    
+    @Test
     public void testCreatePrivateVC () throws KustvaktException {
         createPrivateVC(testUser, "new_vc");
         
@@ -48,15 +67,77 @@
         JsonNode node = listVC(testUser);
         assertEquals(2, node.size());
         assertEquals(node.get(1).get("name").asText(), "new_vc");
+        
+        testCreateVC_sameName(testUser, "new_vc", ResourceType.PRIVATE);
+        
         // delete new VC
         deleteVC("new_vc", testUser, testUser);
         // list VC
         node = listVC(testUser);
         assertEquals(1, node.size());
     }
+    
+    @Test
+    public void testCreateSystemVC () throws KustvaktException {
+        String json = "{\"type\": \"SYSTEM\""
+                + ",\"queryType\": \"VIRTUAL_CORPUS\""
+                + ",\"corpusQuery\": \"pubDate since 1820\"}";
+        String vcName = "new_system_vc";
+        Response response = target().path(API_VERSION).path("vc")
+                .path("~system").path(vcName).request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue("admin", "pass"))
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .put(Entity.json(json));
+        assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+        JsonNode node = listSystemVC("pearl");
+        assertEquals(2, node.size());
+        assertEquals(ResourceType.SYSTEM.displayName(),
+                node.at("/0/type").asText());
+        assertEquals(ResourceType.SYSTEM.displayName(),
+                node.at("/1/type").asText());
+        
+        testDeleteSystemVC_unauthorized(vcName, "dory");
+        testDeleteSystemVC(vcName);
+        
+        node = listSystemVC("pearl");
+        assertEquals(1, node.size());
+    }
 
     @Test
-    public void testCreateVCWithInvalidToken ()
+    public void testCreateSystemVC_unauthorized () throws KustvaktException {
+        String json = "{\"type\": \"SYSTEM\""
+                + ",\"queryType\": \"VIRTUAL_CORPUS\""
+                + ",\"corpusQuery\": \"creationDate since 1820\"}";
+        Response response = target().path(API_VERSION).path("vc")
+                .path("~" + testUser).path("new_vc").request()
+                .header(Attributes.AUTHORIZATION, authHeader)
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .put(Entity.json(json));
+        testResponseUnauthorized(response, testUser);
+    }
+
+    
+    private void testCreateVC_sameName (String username, String vcName,
+            ResourceType vcType) throws KustvaktException {
+        String vcJson = "{\"type\": \"" + vcType + "\""
+                + ",\"queryType\": \"VIRTUAL_CORPUS\""
+                + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+
+        String authHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue(username, "pass");
+
+        Response response = target().path(API_VERSION).path("vc")
+                .path("~" + username).path(vcName).request()
+                .header(Attributes.AUTHORIZATION, authHeader)
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .put(Entity.json(vcJson));
+
+        assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
+    }
+    
+    @Test
+    public void testCreateVC_invalidToken ()
             throws IOException, KustvaktException {
         String json = "{\"type\": \"PRIVATE\","
                 + "\"corpusQuery\": \"corpusSigle=GOE\"}";
@@ -86,7 +167,7 @@
     }
 
     @Test
-    public void testCreateVCWithExpiredToken ()
+    public void testCreateVC_expiredToken ()
             throws IOException, KustvaktException {
         String json = "{\"type\": \"PRIVATE\","
                 + "\"corpusQuery\": \"corpusSigle=GOE\"}";
@@ -113,43 +194,6 @@
     }
 
     @Test
-    public void testCreateSystemVC () throws KustvaktException {
-        String json = "{\"type\": \"SYSTEM\""
-                + ",\"queryType\": \"VIRTUAL_CORPUS\""
-                + ",\"corpusQuery\": \"pubDate since 1820\"}";
-        String vcName = "new_system_vc";
-        Response response = target().path(API_VERSION).path("vc")
-                .path("~system").path(vcName).request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue("admin", "pass"))
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
-                .put(Entity.json(json));
-        assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
-        JsonNode node = listSystemVC("pearl");
-        assertEquals(2, node.size());
-        assertEquals(ResourceType.SYSTEM.displayName(),
-                node.at("/0/type").asText());
-        assertEquals(ResourceType.SYSTEM.displayName(),
-                node.at("/1/type").asText());
-        deleteVC(vcName, "system", "admin");
-        node = listSystemVC("pearl");
-        assertEquals(1, node.size());
-    }
-
-    @Test
-    public void testCreateSystemVC_unauthorized () throws KustvaktException {
-        String json = "{\"type\": \"SYSTEM\""
-                + ",\"queryType\": \"VIRTUAL_CORPUS\""
-                + ",\"corpusQuery\": \"creationDate since 1820\"}";
-        Response response = target().path(API_VERSION).path("vc")
-                .path("~" + testUser).path("new_vc").request()
-                .header(Attributes.AUTHORIZATION, authHeader)
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
-                .put(Entity.json(json));
-        testResponseUnauthorized(response, testUser);
-    }
-
-    @Test
     public void testCreateVC_invalidName () throws KustvaktException {
         String json = "{\"type\": \"PRIVATE\""
                 + ",\"queryType\": \"VIRTUAL_CORPUS\""
@@ -313,14 +357,6 @@
     }
 
     @Test
-    public void testDeleteVC_unauthorized () throws KustvaktException {
-        Response response = target().path(API_VERSION).path("vc").path("~dory")
-                .path("dory-vc").request()
-                .header(Attributes.AUTHORIZATION, authHeader).delete();
-        testResponseUnauthorized(response, testUser);
-    }
-
-    @Test
     public void testEditVC () throws KustvaktException {
         // 1st edit
         String json = "{\"description\": \"edited vc\"}";
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusFieldTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusFieldTest.java
index 5262738..a56c34d 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusFieldTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusFieldTest.java
@@ -86,7 +86,8 @@
         testRetrieveProhibitedField("system", "named-vc1", "tokens");
         testRetrieveProhibitedField("system", "named-vc1", "base");
         VirtualCorpusCache.delete("named-vc1");
-        deleteVcFromDB("named-vc1");
+        
+        deleteVC("named-vc1", "system", "admin");
     }
 
     private void testRetrieveUnknownTokens ()
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusInfoTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusInfoTest.java
index 6129f3e..6d98009 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusInfoTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusInfoTest.java
@@ -35,7 +35,7 @@
     }
 
     @Test
-    public void testRetrieveSystemVCGuest ()
+    public void testRetrieveSystemVC_guest ()
             throws ProcessingException, KustvaktException {
         Response response = target().path(API_VERSION).path("vc")
                 .path("~system").path("system-vc").request().get();
@@ -46,7 +46,7 @@
     }
 
     @Test
-    public void testRetrieveOwnerPrivateVC ()
+    public void testRetrievePrivateVC ()
             throws ProcessingException, KustvaktException {
         JsonNode node = retrieveVCInfo("dory", "dory", "dory-vc");
         assertEquals(node.at("/name").asText(), "dory-vc");
@@ -55,7 +55,7 @@
     }
 
     @Test
-    public void testRetrievePrivateVCUnauthorized ()
+    public void testRetrievePrivateVC_unauthorized ()
             throws ProcessingException, KustvaktException {
         Response response = target().path(API_VERSION).path("vc").path("~dory")
                 .path("dory-vc").request()
@@ -66,7 +66,7 @@
     }
 
     @Test
-    public void testRetrieveProjectVC ()
+    public void testRetrieveProjectVC_member ()
             throws ProcessingException, KustvaktException {
         createDoryGroup();
         inviteMember(doryGroupName, "dory", "nemo");
@@ -91,7 +91,7 @@
     }
 
     @Test
-    public void testRetrieveProjectVCUnauthorized ()
+    public void testRetrieveProjectVC_unauthorized ()
             throws ProcessingException, KustvaktException {
         Response response = target().path(API_VERSION).path("vc").path("~dory")
                 .path("group-vc").request()
@@ -102,7 +102,7 @@
     }
 
     @Test
-    public void testRetrieveProjectVCbyNonActiveMember ()
+    public void testRetrieveProjectVC_nonActiveMember ()
             throws ProcessingException, KustvaktException {
         Response response = target().path(API_VERSION).path("vc").path("~dory")
                 .path("group-vc").request()
@@ -113,7 +113,7 @@
     }
 
     @Test
-    public void testAdminRetrievePrivateVC ()
+    public void testRetrievePrivateVC_admin ()
             throws ProcessingException, KustvaktException {
         Response response = target().path(API_VERSION).path("vc").path("~dory")
                 .path("dory-vc").request()
@@ -128,7 +128,7 @@
     }
 
     @Test
-    public void testAdminRetrieveProjectVC ()
+    public void testRetrieveProjectVC_admin ()
             throws ProcessingException, KustvaktException {
         Response response = target().path(API_VERSION).path("vc").path("~dory")
                 .path("group-vc").request()
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusSharingTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusSharingTest.java
index f0dffd9..9898475 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusSharingTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusSharingTest.java
@@ -15,7 +15,6 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.utils.JsonUtils;
-import jakarta.ws.rs.ProcessingException;
 import jakarta.ws.rs.client.Entity;
 import jakarta.ws.rs.core.Form;
 import jakarta.ws.rs.core.Response;
@@ -26,8 +25,7 @@
     private String testUser = "VirtualCorpusSharingTest";
 
     @Test
-    public void testShareUnknownVC ()
-            throws ProcessingException, KustvaktException {
+    public void testShareUnknownVC () throws KustvaktException {
         Response response = shareVCByCreator("marlin", "non-existing-vc",
                 "marlin group");
         JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
@@ -37,8 +35,7 @@
     }
 
     @Test
-    public void testShareUnknownGroup ()
-            throws ProcessingException, KustvaktException {
+    public void testShareUnknownGroup () throws KustvaktException {
         Response response = shareVCByCreator("marlin", "marlin-vc",
                 "non-existing-group");
         JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
@@ -48,8 +45,7 @@
     }
 
     @Test
-    public void testShareVC_Unauthorized ()
-            throws ProcessingException, KustvaktException {
+    public void testShareVC_Unauthorized () throws KustvaktException {
         Response response = target().path(API_VERSION).path("vc")
                 .path("~marlin").path("marlin-vc").path("share")
                 .path("@marlin group").request()
@@ -60,86 +56,98 @@
     }
 
     @Test
-    public void testShareVC_ByGroupAdmin ()
-            throws ProcessingException, KustvaktException {
+    public void testShareVC_ByGroupAdmin () throws KustvaktException {
         createMarlinGroup();
         inviteMember(marlinGroupName, "marlin", "nemo");
         subscribe(marlinGroupName, "nemo");
-        
+
         JsonNode node = listRolesByGroup("marlin", marlinGroupName);
         assertEquals(0, node.size());
-        
+
         // share by member unauthorized
         Response response = shareVCByCreator("nemo", "nemo-vc",
                 marlinGroupName);
         testResponseUnauthorized(response, "nemo");
-        
+
         Form form = new Form();
         form.param("memberUsername", "nemo");
         form.param("role", PredefinedRole.GROUP_ADMIN.name());
         addMemberRole(marlinGroupName, "marlin", form);
 
         response = shareVCByCreator("nemo", "nemo-vc", marlinGroupName);
-        
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        testShareVC_redundant("nemo", "nemo-vc", marlinGroupName);;
+
         node = listRolesByGroup("marlin", marlinGroupName);
         assertEquals(1, node.size());
         deleteGroupByName(marlinGroupName, "marlin");
     }
 
+    private void testShareVC_redundant (String vcCreator, String vcName,
+            String groupName) throws KustvaktException {
+        Response response = shareVCByCreator(vcCreator, vcName, groupName);
+        assertEquals(Status.CONFLICT.getStatusCode(), response.getStatus());
+        //        JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+        //        System.out.println(node.toPrettyString());
+    }
+
     @Test
     public void testSharePrivateVC () throws KustvaktException {
         String vcName = "new_private_vc";
         createPrivateVC(testUser, vcName);
-        
+
         String groupName = "DNB-group";
         Response response = createUserGroup(groupName, "DNB users", testUser);
         assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
-        
+
         JsonNode roleNodes = listRolesByGroup(testUser, groupName, false);
         assertEquals(5, roleNodes.size());
-        
+
         String memberName = "darla";
         testInviteMember(groupName, testUser, memberName);
         subscribe(groupName, memberName);
-        
+
         roleNodes = listRolesByGroup(testUser, groupName, false);
         assertEquals(6, roleNodes.size());
-        
+
         // share vc to group
         shareVCByCreator(testUser, vcName, groupName);
-        
+
         // check member roles
         JsonNode queryRoleNodes = listRolesByGroup(testUser, groupName);
         assertEquals(1, queryRoleNodes.size());
-        
+
+        testDeleteQueryAccessUnauthorized(testUser, vcName, groupName,
+                memberName);
         testDeleteQueryAccessToGroup(testUser, groupName, vcName);
-        
+
         deleteVC(vcName, testUser, testUser);
         deleteGroupByName(groupName, testUser);
-        
+
         roleNodes = listRolesByGroup(testUser, groupName, false);
         assertEquals(StatusCodes.NO_RESOURCE_FOUND,
                 roleNodes.at("/errors/0/0").asInt());
     }
-    
+
     @Test
     public void testShareProjectVC () throws KustvaktException {
         String vcName = "new_project_vc";
         createProjectVC(testUser, vcName);
-        
+
         // retrieve vc info
         JsonNode vcInfo = retrieveVCInfo(testUser, testUser, vcName);
         assertEquals(vcName, vcInfo.get("name").asText());
-        
+
         // list user VC
         JsonNode node = listVC(testUser);
         assertEquals(2, node.size());
         assertEquals(vcName, node.get(1).get("name").asText());
-        
+
         // search by non member
         Response response = searchWithVCRef("dory", testUser, vcName);
         assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
-        
+
         // create user group
         String groupName = "owidGroup";
         String memberName = "darla";
@@ -149,14 +157,14 @@
         testInviteMember(groupName, testUser, memberName);
         subscribe(groupName, memberName);
         checkMemberInGroup(memberName, testUser, groupName);
-        
+
         // share vc to group
         shareVCByCreator(testUser, vcName, groupName);
-        
+
         // check member roles
         node = listRolesByGroup(testUser, groupName);
         assertEquals(1, node.size());
-        
+
         // search by member
         response = searchWithVCRef(memberName, testUser, vcName);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
@@ -173,59 +181,67 @@
         node = JsonUtils.readTree(response.readEntity(String.class));
         assertEquals(StatusCodes.NO_RESOURCE_FOUND,
                 node.at("/errors/0/0").asInt());
-        
+
         deleteGroupByName(groupName, testUser);
     }
-    
+
     @Test
     public void testShareMultipleVC () throws KustvaktException {
         String vc1 = "new_private_vc";
         String vc2 = "new_project_vc";
         createPrivateVC(testUser, vc1);
         createProjectVC(testUser, vc2);
-        
+
         String groupName = "DNB-group";
         Response response = createUserGroup(groupName, "DNB users", testUser);
         assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
-        
+
         String memberName = "darla";
         testInviteMember(groupName, testUser, memberName);
         subscribe(groupName, memberName);
-        
+
         shareVC(testUser, vc1, groupName, testUser);
         shareVC(testUser, vc2, groupName, testUser);
-        
+
         // list user VC
         JsonNode node = listVC(testUser);
         assertEquals(3, node.size());
-        
+
         node = listVC(memberName);
         assertEquals(3, node.size());
-        
-        deleteRoleByGroupAndQuery(testUser, vc1, groupName, testUser);
-        
+
+        testDeleteQueryAccessBySystemAdmin(testUser, vc1, groupName, "admin");
+
         node = listVC(memberName);
         assertEquals(2, node.size());
-        
+
         node = listVC(testUser);
         assertEquals(3, node.size());
-        
+
+        testDeleteQueryAccessByGroupAdmin(testUser, vc2, groupName, memberName);
+
+        node = listVC(memberName);
+        assertEquals(1, node.size());
+
         deleteVC(vc1, testUser, testUser);
         deleteVC(vc2, testUser, testUser);
-        
+
         node = listVC(testUser);
         assertEquals(1, node.size());
-        
+
         deleteGroupByName(groupName, testUser);
     }
-    
+
     private void testDeleteQueryAccessToGroup (String username,
             String groupName, String vcName) throws KustvaktException {
         JsonNode roleNodes = listRolesByGroup(username, groupName, false);
         assertEquals(7, roleNodes.size());
 
         // delete group role
-        deleteRoleByGroupAndQuery(username, vcName, groupName, username);
+        Response response = deleteRoleByGroupAndQuery(username, vcName,
+                groupName, username);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
         JsonNode queryRoleNodes = listRolesByGroup(username, groupName);
         assertEquals(0, queryRoleNodes.size());
@@ -234,30 +250,59 @@
         assertEquals(6, roleNodes.size());
 
     }
-    
+
+    private void testDeleteQueryAccessUnauthorized (String vcCreator,
+            String vcName, String groupName, String username)
+            throws KustvaktException {
+        Response response = deleteRoleByGroupAndQuery(vcCreator, vcName,
+                groupName, username);
+
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+    }
+
+    private void testDeleteQueryAccessBySystemAdmin (String vcCreator,
+            String vcName, String groupName, String username)
+            throws KustvaktException {
+        Response response = deleteRoleByGroupAndQuery(vcCreator, vcName,
+                groupName, username);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    private void testDeleteQueryAccessByGroupAdmin (String vcCreator,
+            String vcName, String groupName, String memberName)
+            throws KustvaktException {
+
+        addAdminRole(groupName, memberName, vcCreator);
+        Response response = deleteRoleByGroupAndQuery(vcCreator, vcName,
+                groupName, memberName);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
+
     private void testDeleteSharedVC (String vcName, String vcCreator,
             String username, String groupName) throws KustvaktException {
         JsonNode node = listRolesByGroup(username, groupName);
         assertEquals(1, node.size());
-        
+
         Response response = deleteVC(vcName, vcCreator, username);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        
+
         node = listRolesByGroup(username, groupName);
         assertEquals(0, node.size());
     }
 
-//    private JsonNode listUserGroup (String username, String groupName)
-//            throws KustvaktException {
-//        Response response = target().path(API_VERSION).path("group").request()
-//                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-//                        .createBasicAuthorizationHeaderValue(username, "pass"))
-//                .get();
-//        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-//        String entity = response.readEntity(String.class);
-//        JsonNode node = JsonUtils.readTree(entity);
-//        return node;
-//    }
+    //    private JsonNode listUserGroup (String username, String groupName)
+    //            throws KustvaktException {
+    //        Response response = target().path(API_VERSION).path("group").request()
+    //                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+    //                        .createBasicAuthorizationHeaderValue(username, "pass"))
+    //                .get();
+    //        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    //        String entity = response.readEntity(String.class);
+    //        JsonNode node = JsonUtils.readTree(entity);
+    //        return node;
+    //    }
 
     private void checkMemberInGroup (String memberName, String testUser,
             String groupName) throws KustvaktException {
@@ -269,4 +314,15 @@
         assertEquals(PredefinedRole.GROUP_MEMBER.name(),
                 node.at("/members/1/roles/0").asText());
     }
+
+    @Test
+    public void testlistRolesUnauthorized () throws KustvaktException {
+        createDoryGroup();
+        JsonNode node = listRolesByGroup("nemo", "dory-group");
+        assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+                node.at("/errors/0/0").asInt());
+        assertEquals(node.at("/errors/0/1").asText(),
+                "Unauthorized operation for user: nemo");
+        deleteGroupByName(doryGroupName, "dory");
+    }
 }
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusTestBase.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusTestBase.java
index 0d179bb..d9604fb 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusTestBase.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusTestBase.java
@@ -150,7 +150,7 @@
     }
 
     protected Response shareVCByCreator (String vcCreator, String vcName,
-            String groupName) throws ProcessingException, KustvaktException {
+            String groupName) throws KustvaktException {
 
         return target().path(API_VERSION).path("vc").path("~" + vcCreator)
                 .path(vcName).path("share").path("@" + groupName).request()
@@ -244,6 +244,7 @@
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
     }
     
+    @Deprecated
     protected Response deleteAccess (String username, String accessId)
             throws ProcessingException, KustvaktException {
         Response response = target().path(API_VERSION).path("vc").path("access")