Repository: virtool/virtool
Issue: 422 Unprocessable Entity with error: {restrict_source_types: ["unknown field"]}

From #661 

> cannot toggle the Enable checkbox for Source Types (sets boolean value for restrict_source_types field in state.references.detail).
> 
> A patch request to update the reference results in a 422 Unprocessable Entity with error: {restrict_source_types: ["unknown field"]}

Requires a change in API validation to accept `restrict_source_types` value at that endpoint.

Changed files:
virtool/api/users.py, tests/api/test_account.py, virtool/api/references.py, tests/api/test_users.py, tests/api/test_otus.py, virtool/http/routes.py, tests/api/test_samples.py, tests/api/test_groups.py

diff --git a/tests/api/test_account.py b/tests/api/test_account.py
index 8b8583493..1c7a7788b 100644
--- a/tests/api/test_account.py
+++ b/tests/api/test_account.py
@@ -133,8 +133,7 @@ async def test_update_settings(invalid_input, spawn_client, resp_is):
 
     if invalid_input:
         assert await resp_is.invalid_input(resp, {
-            "show_ids": ["must be of boolean type"],
-            "foo_bar": ["unknown field"]
+            "show_ids": ["must be of boolean type"]
         })
     else:
         assert resp.status == 200
diff --git a/tests/api/test_groups.py b/tests/api/test_groups.py
index bb4dd841d..c38662cd7 100644
--- a/tests/api/test_groups.py
+++ b/tests/api/test_groups.py
@@ -91,7 +91,6 @@ class TestCreate:
         })
 
         assert await resp_is.invalid_input(resp, {
-            "test": ["unknown field"],
             "group_id": ["required field"]
         })
 
diff --git a/tests/api/test_otus.py b/tests/api/test_otus.py
index 20e82e27e..d675c8de4 100644
--- a/tests/api/test_otus.py
+++ b/tests/api/test_otus.py
@@ -184,7 +184,6 @@ class TestCreate:
         assert resp.status == 422
 
         assert await resp_is.invalid_input(resp, {
-            "otu_name": ["unknown field"],
             "abbreviation": ["must be of string type"],
             "name": ["required field"]
         })
@@ -396,7 +395,6 @@ class TestEdit:
         assert resp.status == 422
 
         assert await resp_is.invalid_input(resp, {
-            "otu_name": ["unknown field"],
             "abbreviation": ["must be of string type"]
         })
 
@@ -1621,8 +1619,7 @@ class TestCreateSequence:
 
         assert await resp_is.invalid_input(resp, {
             "accession": ["must be of string type"],
-            "sequence": ["required field"],
-            "seq": ["unknown field"]
+            "sequence": ["required field"]
         })
 
     @pytest.mark.parametrize("otu_id, isolate_id", [
@@ -1749,8 +1746,7 @@ class TestEditSequence:
         assert resp.status == 422
 
         assert await resp_is.invalid_input(resp, {
-            "definition": ["must be of string type"],
-            "plant": ["unknown field"]
+            "definition": ["must be of string type"]
         })
 
     @pytest.mark.parametrize("foobar", ["otu_id", "isolate_id", "sequence_id"])
diff --git a/tests/api/test_samples.py b/tests/api/test_samples.py
index 7059afb42..27942a41d 100644
--- a/tests/api/test_samples.py
+++ b/tests/api/test_samples.py
@@ -717,5 +717,5 @@ class TestAnalyze:
         })
 
         assert await resp_is.invalid_input(resp, {
-            "algorithm": ["required field"], "foobar": ["unknown field"], "ref_id": ["required field"]
+            "algorithm": ["required field"], "ref_id": ["required field"]
         })
diff --git a/tests/api/test_users.py b/tests/api/test_users.py
index 543cb6335..17e98a062 100644
--- a/tests/api/test_users.py
+++ b/tests/api/test_users.py
@@ -141,10 +141,9 @@ class TestCreate:
 
         resp = await client.post("/api/users", data)
 
-        assert resp.status == 422
+        print(await resp.json())
 
         assert await resp_is.invalid_input(resp, {
-            "username": ["unknown field"],
             "password": ["must be of string type"],
             "user_id": ["required field"]
         })
@@ -208,7 +207,7 @@ async def test_edit(data, error, spawn_client, resp_is, static_time, create_user
     payload = dict(data)
 
     if error == "invalid_input":
-        payload["foobar"] = "baz"
+        payload["force_reset"] = "baz"
 
     resp = await client.patch("/api/users/bob", payload)
 
@@ -221,7 +220,7 @@ async def test_edit(data, error, spawn_client, resp_is, static_time, create_user
 
     elif error == "invalid_input":
         assert await resp_is.invalid_input(resp, {
-            "foobar": ["unknown field"]
+            "force_reset": ["must be of boolean type"]
         })
 
     elif error == "user_dne":
diff --git a/virtool/api/references.py b/virtool/api/references.py
index 9f63ef34a..a8f1136b1 100644
--- a/virtool/api/references.py
+++ b/virtool/api/references.py
@@ -294,6 +294,9 @@ async def create(req):
     "internal_control": {
         "type": "string"
     },
+    "restrict_source_types": {
+        "type": "boolean"
+    },
     "source_types": {
         "type": "list",
         "schema": {
diff --git a/virtool/api/users.py b/virtool/api/users.py
index 2ef77e066..408d3a41c 100644
--- a/virtool/api/users.py
+++ b/virtool/api/users.py
@@ -51,7 +51,7 @@ async def create(req):
         "user_id": {"type": "string", "minlength": 1, "required": True},
         "password": {"type": "string", "minlength": req.app["settings"]["minimum_password_length"], "required": True},
         "force_reset": {"type": "boolean", "default": True}
-    })
+    }, purge_unknown=True)
 
     if not v.validate(data):
         return invalid_input(v.errors)
@@ -87,7 +87,7 @@ async def edit(req):
         "groups": {"type": "list", "allowed": groups},
         "password": {"type": "string", "minlength": req.app["settings"]["minimum_password_length"]},
         "primary_group": {"type": "string"}
-    })
+    }, purge_unknown=True)
 
     if not v.validate(data):
         return invalid_input(v.errors)
diff --git a/virtool/http/routes.py b/virtool/http/routes.py
index 49c959bdd..4fc6a45fc 100644
--- a/virtool/http/routes.py
+++ b/virtool/http/routes.py
@@ -69,7 +69,7 @@ def protect(route_decorator, admin, permission, public, schema):
                     data = None
 
                 if schema and data is not None:
-                    v = Validator(schema)
+                    v = Validator(schema, purge_unknown=True)
 
                     if not v.validate(data):
                         return invalid_input(v.errors)