From 1a12ad49cfe46bbec08c6f2fab7ff2a569418745 Mon Sep 17 00:00:00 2001
From: Jeff Burke <Jeff.Burke@nrc-cnrc.gc.ca>
Date: Fri, 26 Sep 2014 13:54:23 -0700
Subject: [PATCH] s1651: updated unit tests

---
 projects/cadcAccessControl-Server/build.xml   |   9 +-
 .../cadc/ac/server/RequestValidatorTest.java  | 200 ++++++++++++++
 .../server/web/AddGroupMemberActionTest.java  | 173 ++++++++++++
 .../server/web/AddUserMemberActionTest.java   | 205 ++++++++++++++
 .../ac/server/web/DeleteGroupActionTest.java  | 136 +++++++++
 .../cadc/ac/server/web/GroupsActionTest.java  | 258 ++++++++++++++++++
 .../web/RemoveGroupMemberActionTest.java      | 176 ++++++++++++
 .../web/RemoveUserMemberActionTest.java       | 206 ++++++++++++++
 .../cadc/ac/MemberAlreadyExistsException.java |   9 +
 .../nrc/cadc/ac/MemberNotFoundException.java  |   9 +
 .../nrc/cadc/ac/GroupsReaderWriterTest.java   | 156 +++++++++++
 .../test/src/ca/nrc/cadc/ac/RoleTest.java     | 153 +++++++++++
 12 files changed, 1682 insertions(+), 8 deletions(-)
 create mode 100644 projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/RequestValidatorTest.java
 create mode 100644 projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/AddGroupMemberActionTest.java
 create mode 100644 projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/AddUserMemberActionTest.java
 create mode 100644 projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/DeleteGroupActionTest.java
 create mode 100644 projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupsActionTest.java
 create mode 100644 projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/RemoveGroupMemberActionTest.java
 create mode 100644 projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/RemoveUserMemberActionTest.java
 create mode 100644 projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/GroupsReaderWriterTest.java
 create mode 100644 projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/RoleTest.java

diff --git a/projects/cadcAccessControl-Server/build.xml b/projects/cadcAccessControl-Server/build.xml
index 6bc6d4ec..6799d81d 100644
--- a/projects/cadcAccessControl-Server/build.xml
+++ b/projects/cadcAccessControl-Server/build.xml
@@ -131,10 +131,7 @@
     </target>
     
 <!--    <target name="test" depends="compile-test,resources">
-        <echo message="Running test" />-->
-
-        <!-- Run the junit test suite -->
-<!--        <echo message="Running test suite..." />
+        <echo message="Running test suite..." />
         <junit printsummary="yes" haltonfailure="yes" fork="yes">
             <classpath>
                 <pathelement path="${build}/class"/>
@@ -142,10 +139,6 @@
                 <pathelement path="${testingJars}"/>
             </classpath>
             <test name="ca.nrc.cadc.ac.server.ldap.LdapDAOTest" />
-            <test name="ca.nrc.cadc.ac.server.ldap.LdapGroupDAOTest" />
-            <test name="ca.nrc.cadc.ac.server.web.GroupActionFactoryTest" />
-            <test name="ca.nrc.cadc.ac.server.ldap.LdapUserDAOTest" />
-            <test name="ca.nrc.cadc.ac.server.RequestValidatorTest" />
             <formatter type="plain" usefile="false" />
         </junit>
     </target>-->
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/RequestValidatorTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/RequestValidatorTest.java
new file mode 100644
index 00000000..94760917
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/RequestValidatorTest.java
@@ -0,0 +1,200 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server;
+
+import ca.nrc.cadc.ac.Role;
+import ca.nrc.cadc.ac.server.web.AddUserMemberActionTest;
+import ca.nrc.cadc.auth.AuthenticationUtil;
+import ca.nrc.cadc.util.Log4jInit;
+import ca.nrc.cadc.uws.Parameter;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jburke
+ */
+public class RequestValidatorTest
+{
+    private final static Logger log = Logger.getLogger(AddUserMemberActionTest.class);
+    
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+
+    /**
+     * Test of validate method, of class RequestValidator.
+     */
+    @Test
+    public void testValidate()
+    {
+        try
+        {   
+            RequestValidator rv = new RequestValidator();
+            
+            try
+            {
+                rv.validate(null);
+                fail("null parameter list should throw IllegalArgumentException");
+            }
+            catch (IllegalArgumentException ignore) {}
+            
+            List<Parameter> paramList = new ArrayList<Parameter>();
+            try
+            {
+                rv.validate(paramList);
+                fail("empty parameter list should throw IllegalArgumentException");
+            }
+            catch (IllegalArgumentException ignore) {}
+            
+            paramList.add(new Parameter("IDTYPE", "idtype"));
+            paramList.add(new Parameter("ROLE", "role"));
+            try
+            {
+                rv.validate(paramList);
+                fail("missing ID parameter should throw IllegalArgumentException");
+            }
+            catch (IllegalArgumentException ignore) {}
+            
+            paramList.clear();
+            paramList.add(new Parameter("ID", "foo"));
+            paramList.add(new Parameter("ROLE", "role"));
+            try
+            {
+                rv.validate(paramList);
+                fail("missing IDTYPE parameter should throw IllegalArgumentException");
+            }
+            catch (IllegalArgumentException ignore) {}
+            
+            paramList.clear();
+            paramList.add(new Parameter("ID", "foo"));
+            paramList.add(new Parameter("IDTYPE", "idtype"));
+            try
+            {
+                rv.validate(paramList);
+                fail("missing ROLE parameter should throw IllegalArgumentException");
+            }
+            catch (IllegalArgumentException ignore) {}
+            
+            paramList.clear();
+            paramList.add(new Parameter("ID", "foo"));
+            paramList.add(new Parameter("IDTYPE", AuthenticationUtil.AUTH_TYPE_HTTP));
+            paramList.add(new Parameter("ROLE", "foo"));
+            try
+            {
+                rv.validate(paramList);
+                fail("invalid ROLE parameter should throw IllegalArgumentException");
+            }
+            catch (IllegalArgumentException ignore) {}
+            
+            paramList.clear();
+            paramList.add(new Parameter("ID", "foo"));
+            paramList.add(new Parameter("IDTYPE", AuthenticationUtil.AUTH_TYPE_HTTP));
+            paramList.add(new Parameter("ROLE", "foo"));
+            paramList.add(new Parameter("GROUPID", ""));
+            try
+            {
+                rv.validate(paramList);
+                fail("empty GROUPID parameter value should throw IllegalArgumentException");
+            }
+            catch (IllegalArgumentException ignore) {}
+            
+            paramList.clear();
+            paramList.add(new Parameter("ID", "foo"));
+            paramList.add(new Parameter("IDTYPE", AuthenticationUtil.AUTH_TYPE_HTTP));
+            paramList.add(new Parameter("ROLE", Role.MEMBER.getValue()));
+            rv.validate(paramList);
+            
+            assertNotNull(rv.getPrincipal());
+            assertNotNull(rv.getRole());
+            assertNull(rv.getGroupID());
+            
+            paramList.add(new Parameter("GROUPID", "bar"));
+            rv.validate(paramList);
+            
+            assertNotNull(rv.getPrincipal());
+            assertNotNull(rv.getRole());
+            assertNotNull(rv.getGroupID());
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+}
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/AddGroupMemberActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/AddGroupMemberActionTest.java
new file mode 100644
index 00000000..56fa6154
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/AddGroupMemberActionTest.java
@@ -0,0 +1,173 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server.web;
+
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.GroupAlreadyExistsException;
+import ca.nrc.cadc.ac.server.GroupPersistence;
+import ca.nrc.cadc.util.Log4jInit;
+import java.security.Principal;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.easymock.EasyMock;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jburke
+ */
+public class AddGroupMemberActionTest
+{
+    private final static Logger log = Logger.getLogger(AddGroupMemberActionTest.class);
+    
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+
+    @Test
+    public void testExceptions()
+    {
+        try
+        {
+            Group group = new Group("group", null);
+            Group member = new Group("member", null);
+            group.getGroupMembers().add(member);
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            EasyMock.expect(groupPersistence.getGroup("member")).andReturn(member);
+            EasyMock.replay(groupPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            AddGroupMemberAction action = new AddGroupMemberAction(logInfo, "group", "member")
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+            };
+            
+            try
+            {
+                action.run();
+                fail("duplicate group member should throw GroupAlreadyExistsException");
+            }
+            catch (GroupAlreadyExistsException ignore) {}
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+    @Test
+    public void testRun() throws Exception
+    {
+        try
+        {
+            Group group = new Group("group", null);
+            Group member = new Group("member", null);
+            Group modified = new Group("group", null);
+            modified.getGroupMembers().add(member);
+            
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            EasyMock.expect(groupPersistence.getGroup("member")).andReturn(member);
+            EasyMock.expect(groupPersistence.modifyGroup(group)).andReturn(modified);
+            EasyMock.replay(groupPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            AddGroupMemberAction action = new AddGroupMemberAction(logInfo, "group", "member")
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+            };
+
+            action.run();
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+}
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/AddUserMemberActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/AddUserMemberActionTest.java
new file mode 100644
index 00000000..6488fd76
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/AddUserMemberActionTest.java
@@ -0,0 +1,205 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server.web;
+
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.GroupAlreadyExistsException;
+import ca.nrc.cadc.ac.MemberAlreadyExistsException;
+import ca.nrc.cadc.ac.User;
+import ca.nrc.cadc.ac.server.GroupPersistence;
+import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.auth.AuthenticationUtil;
+import ca.nrc.cadc.util.Log4jInit;
+import java.security.Principal;
+import javax.security.auth.x500.X500Principal;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.easymock.EasyMock;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jburke
+ */
+public class AddUserMemberActionTest
+{
+    private final static Logger log = Logger.getLogger(AddUserMemberActionTest.class);
+    
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testExceptions()
+    {
+        try
+        {   
+            String userID = "foo";
+            String userIDType = AuthenticationUtil.AUTH_TYPE_HTTP;
+            Principal userPrincipal = AuthenticationUtil.createPrincipal(userID, userIDType);
+            User<Principal> user = new User<Principal>(userPrincipal);
+            
+            Group group = new Group("group", null);
+            group.getUserMembers().add(user);
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            EasyMock.replay(groupPersistence);
+            
+            final UserPersistence userPersistence = EasyMock.createMock(UserPersistence.class);
+            EasyMock.expect(userPersistence.getUser(userPrincipal)).andReturn(user);
+            EasyMock.replay(userPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            AddUserMemberAction action = new AddUserMemberAction(logInfo, "group", userID, userIDType)
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+                
+                @Override
+                <T extends Principal> UserPersistence<T> getUserPersistence()
+                {
+                    return userPersistence;
+                };
+            };
+            
+            try
+            {
+                action.run();
+                fail("duplicate group member should throw MemberAlreadyExistsException");
+            }
+            catch (MemberAlreadyExistsException ignore) {}
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testRun() throws Exception
+    {
+        try
+        {
+            String userID = "foo";
+            String userIDType = AuthenticationUtil.AUTH_TYPE_HTTP;
+            Principal userPrincipal = AuthenticationUtil.createPrincipal(userID, userIDType);
+            User<Principal> user = new User<Principal>(userPrincipal);
+            
+            Group group = new Group("group", null);
+            Group modified = new Group("group", null);
+            modified.getUserMembers().add(user);
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            EasyMock.expect(groupPersistence.modifyGroup(group)).andReturn(modified);
+            EasyMock.replay(groupPersistence);
+            
+            final UserPersistence userPersistence = EasyMock.createMock(UserPersistence.class);
+            EasyMock.expect(userPersistence.getUser(userPrincipal)).andReturn(user);
+            EasyMock.replay(userPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            AddUserMemberAction action = new AddUserMemberAction(logInfo, "group", userID, userIDType)
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+                
+                @Override
+                <T extends Principal> UserPersistence<T> getUserPersistence()
+                {
+                    return userPersistence;
+                };
+            };
+            
+            action.run();
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+}
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/DeleteGroupActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/DeleteGroupActionTest.java
new file mode 100644
index 00000000..4562671a
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/DeleteGroupActionTest.java
@@ -0,0 +1,136 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server.web;
+
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.MemberAlreadyExistsException;
+import ca.nrc.cadc.ac.User;
+import ca.nrc.cadc.ac.server.GroupPersistence;
+import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.auth.AuthenticationUtil;
+import ca.nrc.cadc.util.Log4jInit;
+import java.security.Principal;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jburke
+ */
+public class DeleteGroupActionTest
+{
+   private final static Logger log = Logger.getLogger(DeleteGroupActionTest.class);
+    
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+
+    @Test
+    public void testRun()
+    {
+        try
+        {   
+            Group group = new Group("group", null);
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            groupPersistence.deleteGroup("group");
+            EasyMock.expectLastCall().once();
+            EasyMock.replay(groupPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            DeleteGroupAction action = new DeleteGroupAction(logInfo, "group")
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+            };
+
+            action.run();
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+}
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupsActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupsActionTest.java
new file mode 100644
index 00000000..5b871f9a
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/GroupsActionTest.java
@@ -0,0 +1,258 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server.web;
+
+import ca.nrc.cadc.ac.GroupAlreadyExistsException;
+import ca.nrc.cadc.ac.GroupNotFoundException;
+import ca.nrc.cadc.ac.MemberAlreadyExistsException;
+import ca.nrc.cadc.ac.MemberNotFoundException;
+import ca.nrc.cadc.ac.UserNotFoundException;
+import ca.nrc.cadc.net.TransientException;
+import ca.nrc.cadc.util.Log4jInit;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.security.AccessControlException;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.easymock.EasyMock;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jburke
+ */
+public class GroupsActionTest
+{
+    private final static Logger log = Logger.getLogger(GroupsActionTest.class);
+    
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+
+    @Test
+    public void testDoActionAccessControlException() throws Exception
+    {
+        String message = "Unauthorized";
+        int responseCode = 401;
+        Exception e = new AccessControlException("");
+        testDoAction(message, responseCode, e);
+    }
+    
+    @Test
+    public void testDoActionIllegalArgumentException() throws Exception
+    {
+        String message = "message";
+        int responseCode = 400;
+        Exception e = new IllegalArgumentException("message");
+        testDoAction(message, responseCode, e);
+    }
+    
+    @Test
+    public void testDoActionMemberNotFoundException() throws Exception
+    {
+        String message = "Member not found: foo";
+        int responseCode = 404;
+        Exception e = new MemberNotFoundException("foo");
+        testDoAction(message, responseCode, e);
+    }
+    
+    @Test
+    public void testDoActionGroupNotFoundException() throws Exception
+    {
+        String message = "Group not found: foo";
+        int responseCode = 404;
+        Exception e = new GroupNotFoundException("foo");
+        testDoAction(message, responseCode, e);
+    }
+    
+    @Test
+    public void testDoActionUserNotFoundException() throws Exception
+    {
+        String message = "User not found: foo";
+        int responseCode = 404;
+        Exception e = new UserNotFoundException("foo");
+        testDoAction(message, responseCode, e);
+    }
+    
+    @Test
+    public void testDoActionMemberAlreadyExistsException() throws Exception
+    {
+        String message = "Member already exists: foo";
+        int responseCode = 409;
+        Exception e = new MemberAlreadyExistsException("foo");
+        testDoAction(message, responseCode, e);
+    }
+    
+    @Test
+    public void testDoActionGroupAlreadyExistsException() throws Exception
+    {
+        String message = "Group already exists: foo";
+        int responseCode = 409;
+        Exception e = new GroupAlreadyExistsException("foo");
+        testDoAction(message, responseCode, e);
+    }
+    
+    @Test
+    public void testDoActionUnsupportedOperationException() throws Exception
+    {
+        String message = "Not yet implemented.";
+        int responseCode = 501;
+        Exception e = new UnsupportedOperationException();
+        testDoAction(message, responseCode, e);
+    }
+    
+    @Test
+    public void testDoActionTransientException() throws Exception
+    {
+        try
+        {
+            HttpServletResponse response = EasyMock.createMock(HttpServletResponse.class);
+            EasyMock.expect(response.isCommitted()).andReturn(Boolean.FALSE);
+            response.setContentType("text/plain");
+            EasyMock.expectLastCall().once();
+            EasyMock.expect(response.getWriter()).andReturn(new PrintWriter(new StringWriter()));
+            EasyMock.expectLastCall().once();
+            response.setStatus(503);
+            EasyMock.expectLastCall().once();
+            EasyMock.replay(response);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            logInfo.setSuccess(false);
+            EasyMock.expectLastCall().once();
+            logInfo.setMessage("Internal Transient Error: foo");
+            EasyMock.expectLastCall().once();
+            EasyMock.replay(logInfo);
+            
+            GroupsActionImpl action = new GroupsActionImpl(logInfo);
+            action.setException(new TransientException("foo"));
+            action.doAction(null, response);
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+    private void testDoAction(String message, int responseCode, Exception e) throws Exception
+    {
+        try
+        {
+            HttpServletResponse response = EasyMock.createMock(HttpServletResponse.class);
+            EasyMock.expect(response.isCommitted()).andReturn(Boolean.FALSE);
+            response.setContentType("text/plain");
+            EasyMock.expectLastCall().once();
+            EasyMock.expect(response.getWriter()).andReturn(new PrintWriter(new StringWriter()));
+            EasyMock.expectLastCall().once();
+            response.setStatus(responseCode);
+            EasyMock.expectLastCall().once();
+            EasyMock.replay(response);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            logInfo.setMessage(message);
+            EasyMock.expectLastCall().once();
+            EasyMock.replay(logInfo);
+            
+            GroupsActionImpl action = new GroupsActionImpl(logInfo);
+            action.setException(e);
+            action.doAction(null, response);
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+
+    public class GroupsActionImpl extends GroupsAction
+    {
+        Exception exception;
+        
+        public GroupsActionImpl(GroupLogInfo logInfo)
+        {
+            super(logInfo);
+        }
+
+        public Object run() throws Exception
+        {
+            throw exception;
+        }
+
+        public void setException(Exception e)
+        {
+            this.exception = e;
+        }
+    }
+    
+}
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/RemoveGroupMemberActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/RemoveGroupMemberActionTest.java
new file mode 100644
index 00000000..7ccc5abb
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/RemoveGroupMemberActionTest.java
@@ -0,0 +1,176 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server.web;
+
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.GroupAlreadyExistsException;
+import ca.nrc.cadc.ac.GroupNotFoundException;
+import ca.nrc.cadc.ac.server.GroupPersistence;
+import ca.nrc.cadc.util.Log4jInit;
+import java.security.Principal;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jburke
+ */
+public class RemoveGroupMemberActionTest
+{
+    private final static Logger log = Logger.getLogger(RemoveGroupMemberActionTest.class);
+    
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+
+    @Test
+    public void testExceptions()
+    {
+        try
+        {
+            Group group = new Group("group", null);
+            Group member = new Group("member", null);
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            EasyMock.expect(groupPersistence.getGroup("member")).andReturn(member);
+            EasyMock.replay(groupPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            RemoveGroupMemberAction action = new RemoveGroupMemberAction(logInfo, "group", "member")
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+            };
+            
+            try
+            {
+                action.run();
+                fail("unknown group member should throw GroupNotFoundException");
+            }
+            catch (GroupNotFoundException ignore) {}
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+    @Test
+    public void testRun() throws Exception
+    {
+        try
+        {
+            Group member = new Group("member", null);
+            Group group = new Group("group", null);
+            group.getGroupMembers().add(member);
+            
+            Group modified = new Group("group", null);
+            modified.getGroupMembers().add(member);
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            EasyMock.expect(groupPersistence.getGroup("member")).andReturn(member);
+            EasyMock.expect(groupPersistence.modifyGroup(group)).andReturn(modified);
+            EasyMock.replay(groupPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            RemoveGroupMemberAction action = new RemoveGroupMemberAction(logInfo, "group", "member")
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+            };
+
+            action.run();
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+}
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/RemoveUserMemberActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/RemoveUserMemberActionTest.java
new file mode 100644
index 00000000..fea4de56
--- /dev/null
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/RemoveUserMemberActionTest.java
@@ -0,0 +1,206 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.server.web;
+
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.MemberAlreadyExistsException;
+import ca.nrc.cadc.ac.MemberNotFoundException;
+import ca.nrc.cadc.ac.User;
+import ca.nrc.cadc.ac.server.GroupPersistence;
+import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.auth.AuthenticationUtil;
+import ca.nrc.cadc.util.Log4jInit;
+import java.security.Principal;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jburke
+ */
+public class RemoveUserMemberActionTest
+{
+   private final static Logger log = Logger.getLogger(RemoveUserMemberActionTest.class);
+    
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testExceptions()
+    {
+        try
+        {   
+            String userID = "foo";
+            String userIDType = AuthenticationUtil.AUTH_TYPE_HTTP;
+            Principal userPrincipal = AuthenticationUtil.createPrincipal(userID, userIDType);
+            User<Principal> user = new User<Principal>(userPrincipal);
+            
+            Group group = new Group("group", null);
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            EasyMock.replay(groupPersistence);
+            
+            final UserPersistence userPersistence = EasyMock.createMock(UserPersistence.class);
+            EasyMock.expect(userPersistence.getUser(userPrincipal)).andReturn(user);
+            EasyMock.replay(userPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            RemoveUserMemberAction action = new RemoveUserMemberAction(logInfo, "group", userID, userIDType)
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+                
+                @Override
+                <T extends Principal> UserPersistence<T> getUserPersistence()
+                {
+                    return userPersistence;
+                };
+            };
+            
+            try
+            {
+                action.run();
+                fail("unknown group member should throw MemberNotFoundException");
+            }
+            catch (MemberNotFoundException ignore) {}
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testRun() throws Exception
+    {
+        try
+        {
+            String userID = "foo";
+            String userIDType = AuthenticationUtil.AUTH_TYPE_HTTP;
+            Principal userPrincipal = AuthenticationUtil.createPrincipal(userID, userIDType);
+            User<Principal> user = new User<Principal>(userPrincipal);
+            
+            Group group = new Group("group", null);
+            group.getUserMembers().add(user);
+            Group modified = new Group("group", null);
+            
+            final GroupPersistence groupPersistence = EasyMock.createMock(GroupPersistence.class);
+            EasyMock.expect(groupPersistence.getGroup("group")).andReturn(group);
+            EasyMock.expect(groupPersistence.modifyGroup(group)).andReturn(modified);
+            EasyMock.replay(groupPersistence);
+            
+            final UserPersistence userPersistence = EasyMock.createMock(UserPersistence.class);
+            EasyMock.expect(userPersistence.getUser(userPrincipal)).andReturn(user);
+            EasyMock.replay(userPersistence);
+            
+            GroupLogInfo logInfo = EasyMock.createMock(GroupLogInfo.class);
+            
+            RemoveUserMemberAction action = new RemoveUserMemberAction(logInfo, "group", userID, userIDType)
+            {
+                @Override
+                <T extends Principal> GroupPersistence<T> getGroupPersistence()
+                {
+                    return groupPersistence;
+                };
+                
+                @Override
+                <T extends Principal> UserPersistence<T> getUserPersistence()
+                {
+                    return userPersistence;
+                };
+            };
+            
+            action.run();
+        }
+        catch (Throwable t)
+        {
+            log.error(t.getMessage(), t);
+            fail("unexpected error: " + t.getMessage());
+        }
+    }
+    
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/MemberAlreadyExistsException.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/MemberAlreadyExistsException.java
index e1b6d383..1dea9e6e 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/MemberAlreadyExistsException.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/MemberAlreadyExistsException.java
@@ -74,4 +74,13 @@ package ca.nrc.cadc.ac;
  */
 public class MemberAlreadyExistsException extends Exception
 {
+    public MemberAlreadyExistsException()
+    {
+        super();
+    }
+    
+    public MemberAlreadyExistsException(String message)
+    {
+        super(message);
+    }
 }
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/MemberNotFoundException.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/MemberNotFoundException.java
index 64e99998..854bba82 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/MemberNotFoundException.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/MemberNotFoundException.java
@@ -74,4 +74,13 @@ package ca.nrc.cadc.ac;
  */
 public class MemberNotFoundException extends Exception
 {
+    public MemberNotFoundException()
+    {
+        super();
+    }
+    
+    public MemberNotFoundException(String message)
+    {
+        super(message);
+    }
 }
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/GroupsReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/GroupsReaderWriterTest.java
new file mode 100644
index 00000000..3a6d595c
--- /dev/null
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/GroupsReaderWriterTest.java
@@ -0,0 +1,156 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac;
+
+import ca.nrc.cadc.auth.HttpPrincipal;
+import ca.nrc.cadc.auth.OpenIdPrincipal;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import javax.security.auth.x500.X500Principal;
+import org.apache.log4j.Logger;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+/**
+ *
+ * @author jburke
+ */
+public class GroupsReaderWriterTest
+{
+    private static Logger log = Logger.getLogger(GroupsReaderWriterTest.class);
+
+    @Test
+    public void testReaderExceptions()
+        throws Exception
+    {
+        try
+        {
+            String s = null;
+            List<Group> g = GroupsReader.read(s);
+            fail("null String should throw IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e) {}
+        
+        try
+        {
+            InputStream in = null;
+            List<Group> g = GroupsReader.read(in);
+            fail("null InputStream should throw IOException");
+        }
+        catch (IOException e) {}
+        
+        try
+        {
+            Reader r = null;
+            List<Group> g = GroupsReader.read(r);
+            fail("null element should throw ReaderException");
+        }
+        catch (IllegalArgumentException e) {}
+    }
+     
+    @Test
+    public void testWriterExceptions()
+        throws Exception
+    {
+        try
+        {
+            GroupsWriter.write(null, new StringBuilder());
+            fail("null Group should throw WriterException");
+        }
+        catch (WriterException e) {}
+    }
+     
+    @Test
+    public void testMinimalReadWrite()
+        throws Exception
+    {        
+        List<Group> expected = new ArrayList<Group>();
+        expected.add(new Group("group1", null));
+        expected.add(new Group("group2", null));
+        
+        StringBuilder xml = new StringBuilder();
+        GroupsWriter.write(expected, xml);
+        assertFalse(xml.toString().isEmpty());
+        
+        List<Group> actual = GroupsReader.read(xml.toString());
+        assertNotNull(actual);
+        assertEquals(expected.size(), actual.size());
+        assertEquals(expected.get(0), actual.get(0));
+        assertEquals(expected.get(1), actual.get(1));
+    }
+
+}
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/RoleTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/RoleTest.java
new file mode 100644
index 00000000..2c6af140
--- /dev/null
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/RoleTest.java
@@ -0,0 +1,153 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac;
+
+import ca.nrc.cadc.util.Log4jInit;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author jburke
+ */
+public class RoleTest
+{
+    private final static Logger log = Logger.getLogger(RoleTest.class);
+    
+    @BeforeClass
+    public static void setUpClass()
+    {
+        Log4jInit.setLevel("ca.nrc.cadc.ac", Level.INFO);
+    }
+    /**
+     * Test of values method, of class Role.
+     */
+    @Test
+    public void testValues()
+    {
+        Role[] expResult = new Role[] { Role.OWNER, Role.MEMBER, Role.ADMIN };
+        Role[] result = Role.values();
+        assertArrayEquals(expResult, result);
+    }
+
+    /**
+     * Test of valueOf method, of class Role.
+     */
+    @Test
+    public void testValueOf()
+    {
+        assertEquals(Role.OWNER, Role.valueOf("OWNER"));
+        assertEquals(Role.MEMBER, Role.valueOf("MEMBER"));
+        assertEquals(Role.ADMIN, Role.valueOf("ADMIN"));
+    }
+
+    /**
+     * Test of toValue method, of class Role.
+     */
+    @Test
+    public void testToValue()
+    {
+        try
+        {
+            Role.toValue("foo");
+            fail("invalid value should throw IllegalArgumentException");
+        }
+        catch (IllegalArgumentException ignore) {}
+        
+        assertEquals(Role.OWNER, Role.toValue("owner"));
+        assertEquals(Role.MEMBER, Role.toValue("member"));
+        assertEquals(Role.ADMIN, Role.toValue("admin"));
+    }
+
+    /**
+     * Test of getValue method, of class Role.
+     */
+    @Test
+    public void testGetValue()
+    {
+        assertEquals("owner", Role.OWNER.getValue());
+        assertEquals("member", Role.MEMBER.getValue());
+        assertEquals("admin", Role.ADMIN.getValue());
+    }
+
+    /**
+     * Test of checksum method, of class Role.
+     */
+    @Test
+    public void testChecksum()
+    {
+        assertEquals("owner".hashCode(), Role.OWNER.checksum());
+        assertEquals("member".hashCode(), Role.MEMBER.checksum());
+        assertEquals("admin".hashCode(), Role.ADMIN.checksum());
+    }
+    
+}
-- 
GitLab