Repository: virtool/virtool
Issue: Reference users and groups issues

- [ ] editing one permission field resets the others to `false`
- [ ] API handlers do not check if provided user or group exists!

Changed files:
virtool/api/references.py, tests/db/test_references.py, tests/api/test_references.py, virtool/db/references.py

diff --git a/tests/api/test_references.py b/tests/api/test_references.py
index bb342a282..443d6e0ed 100644
--- a/tests/api/test_references.py
+++ b/tests/api/test_references.py
@@ -94,10 +94,7 @@ async def test_add_group_or_user(error, field, mocker, spawn_client, resp_is):
         assert await resp.json() == expected
 
         m_add_group_or_user.assert_called_with(client.db, "foobar", field + "s", {
-            "build": False,
             "modify": True,
-            "modify_otu": False,
-            "remove": False,
             field + "_id": "baz"
         })
 
@@ -129,9 +126,6 @@ async def test_edit_group_or_user(error, field, mocker, spawn_client, resp_is):
         assert await resp.json() == expected
 
         m_edit_group_or_user.assert_called_with(client.db, "foobar", "baz", field + "s", {
-            "build": False,
-            "modify": False,
-            "modify_otu": False,
             "remove": True
         })
 
diff --git a/tests/db/test_references.py b/tests/db/test_references.py
index 3fc961620..b8472eb2d 100644
--- a/tests/db/test_references.py
+++ b/tests/db/test_references.py
@@ -1,19 +1,22 @@
-import gzip
-import json
 import os
-import shutil
 import sys
 
 import pytest
-from aiohttp.test_utils import make_mocked_coro
 
 import virtool.db.references
 import virtool.errors
 
+RIGHTS = {
+    "build": False,
+    "modify": False,
+    "modify_otu": False,
+    "remove": False
+}
+
 TEST_IMPORT_FILE_PATH = os.path.join(sys.path[0], "tests", "test_files", "files", "import.json.gz")
 
 
-@pytest.mark.parametrize("error", [None, "duplicate", "missing"])
+@pytest.mark.parametrize("error", [None, "duplicate", "missing", "missing_member"])
 @pytest.mark.parametrize("field", ["group", "user"])
 @pytest.mark.parametrize("rights", [True, False])
 async def test_add_group_or_user(error, field, rights, test_dbi, static_time):
@@ -21,22 +24,22 @@ async def test_add_group_or_user(error, field, rights, test_dbi, static_time):
     ref_id = "foo"
 
     subdocuments = [
-        {
-            "id": "bar"
-        },
-        {
-            "id": "baz"
-        }
+        {**RIGHTS, "id": "bar"},
+        {**RIGHTS, "id": "baz"}
     ]
 
     if error != "missing":
         await test_dbi.references.insert_one({
-
             "_id": ref_id,
             "groups": subdocuments,
             "users": subdocuments
         })
 
+    if error != "missing_member":
+        for _id in ["bar", "buzz"]:
+            await test_dbi.groups.insert_one({"_id": _id})
+            await test_dbi.users.insert_one({"_id": _id})
+
     subdocument_id = "bar" if error == "duplicate" else "buzz"
 
     payload = {
@@ -48,11 +51,15 @@ async def test_add_group_or_user(error, field, rights, test_dbi, static_time):
 
     task = virtool.db.references.add_group_or_user(test_dbi, ref_id, field + "s", payload)
 
-    if error == "duplicate":
+    if error == "duplicate" or error == "missing_member":
         with pytest.raises(virtool.errors.DatabaseError) as err:
             await task
 
-        assert field + " already exists" in str(err)
+        if error == "duplicate":
+            assert field + " already exists" in str(err)
+
+        else:
+            assert field + " does not exist" in str(err)
 
     elif error == "missing":
         assert await task is None
@@ -98,12 +105,8 @@ async def test_edit_group_or_user(field, missing, test_dbi, static_time):
     ref_id = "foo"
 
     subdocuments = [
-        {
-            "id": "bar"
-        },
-        {
-            "id": "baz"
-        }
+        {**RIGHTS, "id": "bar"},
+        {**RIGHTS, "id": "baz"}
     ]
 
     if missing != "reference":
diff --git a/virtool/api/references.py b/virtool/api/references.py
index d09161284..819dcfa9e 100644
--- a/virtool/api/references.py
+++ b/virtool/api/references.py
@@ -15,27 +15,23 @@ import virtool.http.routes
 import virtool.otus
 import virtool.references
 import virtool.utils
-from virtool.api.utils import compose_regex_query, conflict, json_response, no_content, not_found, paginate
+from virtool.api.utils import bad_request, compose_regex_query, conflict, json_response, no_content, not_found, paginate
 
 routes = virtool.http.routes.Routes()
 
 
 RIGHTS_SCHEMA = {
     "build": {
-        "type": "boolean",
-        "default": False
+        "type": "boolean"
     },
     "modify": {
-        "type": "boolean",
-        "default": False
+        "type": "boolean"
     },
     "modify_otu": {
-        "type": "boolean",
-        "default": False
+        "type": "boolean"
     },
     "remove": {
-        "type": "boolean",
-        "default": False
+        "type": "boolean"
     }
 }
 
@@ -569,8 +565,14 @@ async def add_group(req):
 
     try:
         subdocument = await virtool.db.references.add_group_or_user(db, ref_id, "groups", data)
-    except virtool.errors.DatabaseError:
-        raise conflict("Group already exists")
+    except virtool.errors.DatabaseError as err:
+        if "already exists" in str(err):
+            return conflict("Group already exists")
+
+        if "does not exist" in str(err):
+            return bad_request("Group does not exist")
+
+        raise
 
     if subdocument is None:
         return not_found()
@@ -595,8 +597,14 @@ async def add_user(req):
 
     try:
         subdocument = await virtool.db.references.add_group_or_user(db, ref_id, "users", data)
-    except virtool.errors.DatabaseError:
-        raise conflict("Group already exists")
+    except virtool.errors.DatabaseError as err:
+        if "already exists" in str(err):
+            return conflict("User already exists")
+
+        if "does not exist" in str(err):
+            return bad_request("User does not exist")
+
+        raise
 
     if subdocument is None:
         return not_found()
diff --git a/virtool/db/references.py b/virtool/db/references.py
index b69c7d1fd..3575e9b4f 100644
--- a/virtool/db/references.py
+++ b/virtool/db/references.py
@@ -52,6 +52,12 @@ async def add_group_or_user(db, ref_id, field, data):
 
     subdocument_id = data.get("group_id", None) or data["user_id"]
 
+    if field == "groups" and await db.groups.count({"_id": subdocument_id}) == 0:
+        raise virtool.errors.DatabaseError("group does not exist")
+
+    if field == "users" and await db.users.count({"_id": subdocument_id}) == 0:
+        raise virtool.errors.DatabaseError("user does not exist")
+
     if subdocument_id in [s["id"] for s in document[field]]:
         raise virtool.errors.DatabaseError(field[:-1] + " already exists")
 
@@ -270,7 +276,7 @@ async def edit_group_or_user(db, ref_id, subdocument_id, field, data):
 
     for subdocument in document[field]:
         if subdocument["id"] == subdocument_id:
-            rights = {key: data.get(key, False) for key in virtool.references.RIGHTS}
+            rights = {key: data.get(key, subdocument[key]) for key in virtool.references.RIGHTS}
             subdocument.update(rights)
 
             await db.references.update_one({"_id": ref_id}, {