Update add and delete member roles. Deprecate edit member roles. (#763)

Change-Id: Ice8596c63b64ccf257984b04c8ccf2cf91515b44
diff --git a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
index 5481d30..f432ece 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
@@ -16,6 +16,7 @@
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
+import de.ids_mannheim.korap.constant.PredefinedRole;
 import de.ids_mannheim.korap.constant.PrivilegeType;
 import de.ids_mannheim.korap.entity.Role;
 import de.ids_mannheim.korap.entity.Role_;
@@ -28,7 +29,6 @@
  * 
  * @author margaretha
  * @see Role
- * @see PrivilegeDao
  */
 @Transactional
 @Repository
@@ -37,45 +37,42 @@
     @PersistenceContext
     private EntityManager entityManager;
 
-    @Autowired
-    private PrivilegeDao privilegeDao;
+//    public void deleteRole (int roleId) {
+//        Role r = retrieveRoleById(roleId);
+//        entityManager.remove(r);
+//    }
+//
+//    public void editRoleName (int roleId, PredefinedRole name) {
+//        Role r = retrieveRoleById(roleId);
+//        r.setName(name);
+//        entityManager.persist(r);
+//    }
 
-    public void createRole (String name, List<PrivilegeType> privilegeTypes) {
-        Role r = new Role();
-        r.setName(name);
-        entityManager.persist(r);
-        privilegeDao.addPrivilegesToRole(r, privilegeTypes);
+
+    public void addRole (Role newRole) {
+        entityManager.persist(newRole);
+        entityManager.flush();
     }
-
-    public void deleteRole (int roleId) {
-        Role r = retrieveRoleById(roleId);
-        entityManager.remove(r);
-    }
-
-    public void editRoleName (int roleId, String name) {
-        Role r = retrieveRoleById(roleId);
-        r.setName(name);
-        entityManager.persist(r);
-    }
-
-    public Role retrieveRoleById (int roleId) {
+    
+    public Role retrieveRoleByName (PredefinedRole role) {
         CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
         CriteriaQuery<Role> query = criteriaBuilder.createQuery(Role.class);
 
         Root<Role> root = query.from(Role.class);
-        root.fetch(Role_.privileges);
+//        root.fetch(Role_.privileges);
         query.select(root);
-        query.where(criteriaBuilder.equal(root.get(Role_.id), roleId));
+        query.where(criteriaBuilder.equal(root.get(Role_.name), role));
         Query q = entityManager.createQuery(query);
         return (Role) q.getSingleResult();
     }
 
+    @Deprecated
     public Role retrieveRoleByName (String roleName) {
         CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
         CriteriaQuery<Role> query = criteriaBuilder.createQuery(Role.class);
 
         Root<Role> root = query.from(Role.class);
-        root.fetch(Role_.privileges);
+//        root.fetch(Role_.privileges);
         query.select(root);
         query.where(criteriaBuilder.equal(root.get(Role_.name), roleName));
         Query q = entityManager.createQuery(query);
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 bfa8b45..edab0d3 100644
--- a/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
+++ b/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
@@ -571,7 +571,7 @@
     }
 
     public void editMemberRoles (String username, String groupName,
-            String memberUsername, List<PredefinedRole> roleIds)
+            String memberUsername, List<PredefinedRole> roleList)
             throws KustvaktException {
 
         ParameterChecker.checkStringValue(username, "username");
@@ -597,8 +597,8 @@
             }
 
             Set<Role> roles = new HashSet<>();
-            for (int i = 0; i < roleIds.size(); i++) {
-                roles.add(roleDao.retrieveRoleByName(roleIds.get(i)));
+            for (int i = 0; i < roleList.size(); i++) {
+                roles.add(roleDao.retrieveRoleByName(roleList.get(i)));
             }
             member.setRoles(roles);
             groupMemberDao.updateMember(member);
@@ -651,7 +651,7 @@
     }
 
     public void deleteMemberRoles (String username, String groupName,
-            String memberUsername, List<Integer> roleIds)
+            String memberUsername, List<PredefinedRole> rolesToBeDeleted)
             throws KustvaktException {
 
         ParameterChecker.checkStringValue(username, "username");
@@ -669,7 +669,7 @@
             Set<Role> roles = member.getRoles();
             Iterator<Role> i = roles.iterator();
             while (i.hasNext()) {
-                if (roleIds.contains(i.next().getId())) {
+                if (rolesToBeDeleted.contains(i.next().getName())) {
                     i.remove();
                 }
             }
diff --git a/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java b/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
index eb6f046..c2893df 100644
--- a/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
+++ b/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
@@ -222,7 +222,8 @@
         }
     }
 
-    /**
+    /** DEPRECATED for simplicity and easier maintenance.
+     * 
      * Very similar to addMemberRoles web-service, but allows deletion
      * as well.
      * 
@@ -235,20 +236,21 @@
      *            a role id or multiple role ids
      * @return
      */
+    @Deprecated
     @POST
     @Path("@{groupName}/role/edit")
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response editMemberRoles (@Context SecurityContext securityContext,
             @PathParam("groupName") String groupName,
             @FormParam("memberUsername") String memberUsername,
-            @FormParam("roleId") List<PredefinedRole> roleIds) {
+            @FormParam("roles") List<PredefinedRole> roles) {
         TokenContext context = (TokenContext) securityContext
                 .getUserPrincipal();
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.EDIT_USER_GROUP_MEMBER_ROLE);
             service.editMemberRoles(context.getUsername(), groupName,
-                    memberUsername, roleIds);
+                    memberUsername, roles);
             return Response.ok("SUCCESS").build();
         }
         catch (KustvaktException e) {
@@ -275,7 +277,7 @@
     public Response addMemberRoles (@Context SecurityContext securityContext,
             @PathParam("groupName") String groupName,
             @FormParam("memberUsername") String memberUsername,
-            @FormParam("roleId") List<PredefinedRole> roles) {
+            @FormParam("role") List<PredefinedRole> roles) {
         TokenContext context = (TokenContext) securityContext
                 .getUserPrincipal();
         try {
@@ -310,14 +312,14 @@
     public Response deleteMemberRoles (@Context SecurityContext securityContext,
             @PathParam("groupName") String groupName,
             @FormParam("memberUsername") String memberUsername,
-            @FormParam("roleId") List<Integer> roleIds) {
+            @FormParam("role") List<PredefinedRole> roles) {
         TokenContext context = (TokenContext) securityContext
                 .getUserPrincipal();
         try {
             scopeService.verifyScope(context,
                     OAuth2Scope.DELETE_USER_GROUP_MEMBER_ROLE);
             service.deleteMemberRoles(context.getUsername(), groupName,
-                    memberUsername, roleIds);
+                    memberUsername, roles);
             return Response.ok("SUCCESS").build();
         }
         catch (KustvaktException e) {
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java
similarity index 100%
rename from src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
rename to src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerTest.java
similarity index 85%
rename from src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
rename to src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerTest.java
index 61c2a2a..4b7bd19 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerTest.java
@@ -1,24 +1,17 @@
-package de.ids_mannheim.korap.web.controller;
+package de.ids_mannheim.korap.web.controller.usergroup;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.util.Set;
 
-import jakarta.ws.rs.core.Form;
-import jakarta.ws.rs.core.MediaType;
-
 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 jakarta.ws.rs.ProcessingException;
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.Response.Status;
-import jakarta.ws.rs.client.Entity;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.config.SpringJerseyTest;
 import de.ids_mannheim.korap.constant.GroupMemberStatus;
 import de.ids_mannheim.korap.constant.PredefinedRole;
 import de.ids_mannheim.korap.dao.UserGroupMemberDao;
@@ -27,46 +20,30 @@
 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.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
 
 /**
  * @author margaretha
  */
 public class UserGroupControllerTest extends UserGroupTestBase {
 
-    @Autowired
-    private UserGroupMemberDao memberDao;
-
     private String username = "UserGroupControllerTest";
 
     private String admin = "admin";
 
-    private JsonNode retrieveUserGroups (String username)
-            throws ProcessingException, KustvaktException {
-        Response response = target().path(API_VERSION).path("group").request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue(username, "pass"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").get();
-        String entity = response.readEntity(String.class);
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        return JsonUtils.readTree(entity);
-    }
-
-    private void deleteGroupByName (String groupName) throws KustvaktException {
-        Response response = target().path(API_VERSION).path("group")
-                .path("@" + groupName).request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue(username, "pass"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-    }
-
+    
     @Test
     public void testCreateGroupEmptyDescription ()
             throws ProcessingException, KustvaktException {
         String groupName = "empty_group";
         Response response = createUserGroup(groupName, "", username);
         assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
-        deleteGroupByName(groupName);
+        deleteGroupByName(groupName,username);
     }
 
     @Test
@@ -75,7 +52,7 @@
         String groupName = "missing-desc-group";
         Response response = testCreateGroupWithoutDescription(groupName);
         assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
-        deleteGroupByName(groupName);
+        deleteGroupByName(groupName,username);
     }
 
     private Response testCreateGroupWithoutDescription (String groupName)
@@ -127,7 +104,7 @@
         response = testCreateGroupWithoutDescription(groupName);
         assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
         // list user group
-        JsonNode node = retrieveUserGroups(username);
+        JsonNode node = listUserGroups(username);
         assertEquals(1, node.size());
         node = node.get(0);
         assertEquals(node.get("name").asText(), "new-user-group");
@@ -153,7 +130,7 @@
         String description = "Description is updated.";
         Response response = createUserGroup(groupName, description, username);
         assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
-        JsonNode node = retrieveUserGroups(username);
+        JsonNode node = listUserGroups(username);
         assertEquals(1, node.size());
         assertEquals(description, node.get(0).get("description").asText());
     }
@@ -206,7 +183,7 @@
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").delete();
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         // check member
-        JsonNode node = retrieveUserGroups("pearl");
+        JsonNode node = listUserGroups("pearl");
         assertEquals(0, node.size());
     }
 
@@ -324,7 +301,7 @@
             throws ProcessingException, KustvaktException {
         inviteMember(groupName, invitor, invitee);
         // list group
-        JsonNode node = listUserGroup(invitor);
+        JsonNode node = listUserGroups(invitor);
         node = node.get(0);
         assertEquals(2, node.get("members").size());
         assertEquals(node.at("/members/1/userId").asText(), invitee);
@@ -345,7 +322,7 @@
                 .post(Entity.form(form));
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         // check member
-        JsonNode node = retrieveUserGroups("marlin");
+        JsonNode node = listUserGroups("marlin");
         assertEquals(1, node.size());
         JsonNode group = node.get(0);
         assertEquals(GroupMemberStatus.PENDING.name(),
@@ -366,7 +343,7 @@
                 .post(Entity.form(form));
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         // check member
-        JsonNode node = retrieveUserGroups("pearl");
+        JsonNode node = listUserGroups("pearl");
         assertEquals(1, node.size());
         JsonNode group = node.get(0);
         assertEquals(GroupMemberStatus.PENDING.name(),
@@ -450,10 +427,10 @@
     public void testSubscribePendingMember () throws KustvaktException {
         createDoryGroup();
         testInviteMember(doryGroupName, "dory", "marlin");
-        subscribe("@"+doryGroupName, "marlin");
+        subscribe(doryGroupName, "marlin");
         
         // retrieve marlin group
-        JsonNode node = retrieveUserGroups("marlin");
+        JsonNode node = listUserGroups("marlin");
         assertEquals(1, node.size());
         JsonNode group = node.get(0);
         assertEquals(group.at("/name").asText(), "dory-group");
@@ -562,7 +539,7 @@
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
                 .delete();
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        JsonNode node = retrieveUserGroups("marlin");
+        JsonNode node = listUserGroups("marlin");
         assertEquals(0, node.size());
     }
 
@@ -610,7 +587,7 @@
     @Test
     public void testUnsubscribePendingMember ()
             throws ProcessingException, KustvaktException {
-        JsonNode node = retrieveUserGroups("marlin");
+        JsonNode node = listUserGroups("marlin");
         assertEquals(2, node.size());
         Response response = target().path(API_VERSION).path("group")
                 .path("@dory-group").path("unsubscribe").request()
@@ -619,7 +596,7 @@
                         .createBasicAuthorizationHeaderValue("marlin", "pass"))
                 .delete();
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        node = retrieveUserGroups("marlin");
+        node = listUserGroups("marlin");
         assertEquals(1, node.size());
         // invite marlin to dory-group to set back the
         // GroupMemberStatus.PENDING
@@ -687,72 +664,5 @@
                 "Group new-user-group has been deleted.");
     }
 
-    @Test
-    public void testAddSameMemberRole ()
-            throws ProcessingException, KustvaktException {
-        Form form = new Form();
-        form.param("memberUsername", "dory");
-        form.param("roleId", "1");
-        Response response = target().path(API_VERSION).path("group")
-                .path("@marlin-group").path("role").path("add").request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .post(Entity.form(form));
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        UserGroupMember member = memberDao.retrieveMemberById("dory", 1);
-        Set<Role> roles = member.getRoles();
-        assertEquals(2, roles.size());
-    }
 
-    @Test
-    public void testDeleteAddMemberRole ()
-            throws ProcessingException, KustvaktException {
-        Form form = new Form();
-        form.param("memberUsername", "dory");
-        form.param("roleId", "1");
-        Response response = target().path(API_VERSION).path("group")
-                .path("@marlin-group").path("role").path("delete").request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .post(Entity.form(form));
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        UserGroupMember member = memberDao.retrieveMemberById("dory", 1);
-        Set<Role> roles = member.getRoles();
-        assertEquals(1, roles.size());
-        testAddSameMemberRole();
-    }
-
-    @Test
-    public void testEditMemberRoleEmpty ()
-            throws ProcessingException, KustvaktException {
-        Form form = new Form();
-        form.param("memberUsername", "dory");
-        Response response = target().path(API_VERSION).path("group")
-                .path("@marlin-group").path("role").path("edit").request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .post(Entity.form(form));
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        UserGroupMember member = memberDao.retrieveMemberById("dory", 1);
-        Set<Role> roles = member.getRoles();
-        assertEquals(0, roles.size());
-        testEditMemberRole();
-    }
-
-    private void testEditMemberRole ()
-            throws ProcessingException, KustvaktException {
-        Form form = new Form();
-        form.param("memberUsername", "dory");
-        form.param("roleId", "1");
-        form.param("roleId", "3");
-        Response response = target().path(API_VERSION).path("group")
-                .path("@marlin-group").path("role").path("edit").request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue("marlin", "pass"))
-                .post(Entity.form(form));
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        UserGroupMember member = memberDao.retrieveMemberById("dory", 1);
-        Set<Role> roles = member.getRoles();
-        assertEquals(2, roles.size());
-    }
 }
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupListTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupListTest.java
similarity index 84%
rename from src/test/java/de/ids_mannheim/korap/web/controller/UserGroupListTest.java
rename to src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupListTest.java
index 6a3100d..0b30126 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupListTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupListTest.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.web.controller;
+package de.ids_mannheim.korap.web.controller.usergroup;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -21,7 +21,7 @@
         inviteMember(doryGroupName, "dory", "marlin");
         inviteMember(doryGroupName, "dory", "nemo");
         
-        JsonNode node = listUserGroup("dory");
+        JsonNode node = listUserGroups("dory");
         JsonNode group = node.get(0);
         assertEquals(group.at("/name").asText(), "dory-group");
         assertEquals(group.at("/owner").asText(), "dory");
@@ -29,12 +29,15 @@
         
         testListNemoGroups();
         testListMarlinGroups();
+        
+        deleteGroupByName(doryGroupName,"dory");
+        deleteGroupByName(marlinGroupName, "marlin");
     }
     
     public void testListNemoGroups () throws KustvaktException {
-        subscribe("@"+doryGroupName, "nemo");
+        subscribe(doryGroupName, "nemo");
 
-        JsonNode node = listUserGroup("nemo");
+        JsonNode node = listUserGroups("nemo");
         assertEquals(node.at("/0/name").asText(), "dory-group");
         assertEquals(node.at("/0/owner").asText(), "dory");
         // group members are not allowed to see other members
@@ -44,9 +47,9 @@
     // marlin has 2 groups
     public void testListMarlinGroups () throws KustvaktException {
         createMarlinGroup();
-        subscribe("@"+doryGroupName, "marlin");
+        subscribe(doryGroupName, "marlin");
         
-        JsonNode node = listUserGroup("marlin");
+        JsonNode node = listUserGroups("marlin");
         assertEquals(2, node.size());
     }
     
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
new file mode 100644
index 0000000..d8bc63f
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupMemberTest.java
@@ -0,0 +1,118 @@
+package de.ids_mannheim.korap.web.controller.usergroup;
+
+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 de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+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 jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+public class UserGroupMemberTest extends UserGroupTestBase {
+
+    @Autowired
+    private UserGroupMemberDao memberDao;
+
+    @Test
+    public void testAddMemberRole () throws KustvaktException {
+        createMarlinGroup();
+        inviteMember(marlinGroupName, "marlin", "dory");
+        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.USER_GROUP_ADMIN_READ.name());
+
+        addMemberRole(marlinGroupName, "marlin", form);
+
+        UserGroupMember member = memberDao.retrieveMemberById("dory", groupId);
+        Set<Role> roles = member.getRoles();
+        assertEquals(3, roles.size());
+
+        testAddSameMemberRole(groupId);
+        testDeleteMemberRole(groupId);
+        testEditMemberRoleEmpty(groupId);
+        //        testEditMemberRole(groupId);
+
+        deleteGroupByName(marlinGroupName, "marlin");
+    }
+
+    private void testAddSameMemberRole (int groupId)
+            throws ProcessingException, KustvaktException {
+        Form form = new Form();
+        form.param("memberUsername", "dory");
+        form.param("role", PredefinedRole.USER_GROUP_MEMBER_DELETE.name());
+
+        addMemberRole(marlinGroupName, "marlin", form);
+
+        UserGroupMember member = memberDao.retrieveMemberById("dory", groupId);
+        Set<Role> roles = member.getRoles();
+        assertEquals(3, roles.size());
+    }
+
+    private void testDeleteMemberRole (int groupId)
+            throws ProcessingException, KustvaktException {
+        Form form = new Form();
+        form.param("memberUsername", "dory");
+        form.param("role", PredefinedRole.USER_GROUP_ADMIN_READ.name());
+        Response response = target().path(API_VERSION).path("group")
+                .path("@marlin-group").path("role").path("delete").request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue("marlin", "pass"))
+                .post(Entity.form(form));
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        UserGroupMember member = memberDao.retrieveMemberById("dory", groupId);
+        Set<Role> roles = member.getRoles();
+        assertEquals(2, roles.size());
+    }
+
+    @Deprecated
+    private void testEditMemberRoleEmpty (int groupId)
+            throws ProcessingException, KustvaktException {
+        Form form = new Form();
+        form.param("memberUsername", "dory");
+        Response response = target().path(API_VERSION).path("group")
+                .path("@marlin-group").path("role").path("edit").request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue("marlin", "pass"))
+                .post(Entity.form(form));
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        UserGroupMember member = memberDao.retrieveMemberById("dory", groupId);
+        Set<Role> roles = member.getRoles();
+        assertEquals(0, roles.size());
+    }
+
+    @Deprecated
+    private void testEditMemberRole (int groupId)
+            throws ProcessingException, KustvaktException {
+        Form form = new Form();
+        form.param("memberUsername", "dory");
+        form.param("roleId", "1");
+        form.param("roleId", "3");
+        Response response = target().path(API_VERSION).path("group")
+                .path("@marlin-group").path("role").path("edit").request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue("marlin", "pass"))
+                .post(Entity.form(form));
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        UserGroupMember member = memberDao.retrieveMemberById("dory", groupId);
+        Set<Role> roles = member.getRoles();
+        assertEquals(2, roles.size());
+    }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupTestBase.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupTestBase.java
similarity index 74%
rename from src/test/java/de/ids_mannheim/korap/web/controller/UserGroupTestBase.java
rename to src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupTestBase.java
index 889f913..86fe916 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupTestBase.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupTestBase.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.web.controller;
+package de.ids_mannheim.korap.web.controller.usergroup;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -33,12 +33,23 @@
         return response;
     }
 
-    protected JsonNode listUserGroup (String username)
+    protected void deleteGroupByName (String groupName,String username)
+            throws KustvaktException {
+        Response response = target().path(API_VERSION).path("group")
+                .path("@" + groupName).request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .delete();
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    protected JsonNode listUserGroups (String username)
             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;
@@ -61,12 +72,22 @@
     protected void subscribe (String groupName, String username)
             throws KustvaktException {
         Response response = target().path(API_VERSION).path("group")
-                .path(groupName).path("subscribe").request()
+                .path("@"+groupName).path("subscribe").request()
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
                         .createBasicAuthorizationHeaderValue(username, "pass"))
                 .post(Entity.form(new Form()));
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
     }
+    
+    protected void addMemberRole (String groupName, String username,
+            Form form) throws KustvaktException {
+        Response response = target().path(API_VERSION).path("group")
+                .path("@"+groupName).path("role").path("add").request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .post(Entity.form(form));
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
 
     protected JsonNode createDoryGroup ()
             throws ProcessingException, KustvaktException {