diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index 2fc3cfe..38638a0 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -34,7 +34,8 @@ core_id=$!
 
 sleep 2;
 
-tavern-ci --disable-warnings $source_dir
+export PYTHONPATH=$PYTHONPATH:$source_dir/tavern_utils
+tavern-ci --disable-warnings $source_dir/tavern_tests
 test_result=$?
 
 kill $core_id
diff --git a/tests/tavern_tests/controller_relays_basic.tavern.yaml b/tests/tavern_tests/controller_relays_basic.tavern.yaml
new file mode 100644
index 0000000..ee59c11
--- /dev/null
+++ b/tests/tavern_tests/controller_relays_basic.tavern.yaml
@@ -0,0 +1,139 @@
+test_name: Test basic controller relays functions
+
+stages:
+- name: "[controller_relays_basic] discover controllers"
+  request:
+    method: POST
+    url: "http://localhost:5000/api/v1/controllers/discover/"
+  response:
+    status_code: 200
+    json:
+    - id: !anystr
+      name: !anystr
+      relay_count: !anyint
+      relays: !anystr
+      active: !anybool
+      port: !anyint
+      ip: !anystr
+      relays: !anylist
+    save:
+      json:
+        returned_id: "[0].id"
+
+- name: "[controller_relays_basic] get controller relays, check length"
+  request:
+    method: GET
+    url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays"
+  response:
+    status_code: 200
+    json:
+    - name: !anystr
+      number: 0
+      controller_id: "{returned_id}"
+      active_schedule:
+        id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 1
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 2
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 3
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 4
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 5
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 6
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 7
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 8
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+    - name: !anystr
+      number: 9
+      controller_id: "{returned_id}"
+      active_schedule: !anydict
+      schedules: !anylist
+      tags: !anylist
+
+- name: "[controller_relays_basic] get controller relays, check length"
+  request:
+    method: GET
+    url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/5"
+  response:
+    status_code: 200
+    json:
+      name: !anystr
+      number: 5
+      controller_id: "{returned_id}"
+      active_schedule:
+        id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      schedules: 
+      - id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      - id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      - id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      - id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      - id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      - id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      - id: !anystr
+        name: !anystr
+        periods: !anylist
+        tags: !anylist
+      tags: !anylist
diff --git a/tests/test_controllers_basic.tavern.yaml b/tests/tavern_tests/controllers_basic.tavern.yaml
similarity index 83%
rename from tests/test_controllers_basic.tavern.yaml
rename to tests/tavern_tests/controllers_basic.tavern.yaml
index 8458ffa..c7bc774 100644
--- a/tests/test_controllers_basic.tavern.yaml
+++ b/tests/tavern_tests/controllers_basic.tavern.yaml
@@ -1,7 +1,7 @@
 test_name: Test basic controller functions
 
 stages:
-- name: "[test_controllers_basic] discover controllers"
+- name: "[controllers_basic] discover controllers"
   request:
     method: POST
     url: "http://localhost:5000/api/v1/controllers/discover/"
@@ -21,7 +21,7 @@ stages:
         returned_name: "[0].name"
         returned_id: "[0].id"
 
-- name: "[test_controllers_basic] get controller, check name"
+- name: "[controllers_basic] get controller, check name"
   request:
     method: GET
     url: "http://localhost:5000/api/v1/controllers/{returned_id}"
@@ -37,7 +37,7 @@ stages:
       ip: !anystr
       relays: !anylist
 
-- name: "[test_controllers_basic] get controller, check name"
+- name: "[controllers_basic] get controller, check name"
   request:
     method: PUT
     url: "http://localhost:5000/api/v1/controllers/{returned_id}"
@@ -58,21 +58,21 @@ stages:
       json:
         changed_name: "name"
 
-- name: "[test_controllers_basic] delete controller"
+- name: "[controllers_basic] delete controller"
   request:
     method: DELETE
     url: "http://localhost:5000/api/v1/controllers/{returned_id}"
   response:
     status_code: 200
 
-- name: "[test_controllers_basic] get controller, expect 404"
+- name: "[controllers_basic] get controller, expect 404"
   request:
     method: GET
     url: "http://localhost:5000/api/v1/controllers/{returned_id}"
   response:
     status_code: 404
 
-- name: "[test_controllers_basic] discover controllers again"
+- name: "[controllers_basic] discover controllers again"
   request:
     method: POST
     url: "http://localhost:5000/api/v1/controllers/discover/"
@@ -88,7 +88,7 @@ stages:
       ip: !anystr
       relays: !anylist
 
-- name: "[test_controllers_basic] get controller again, check name"
+- name: "[controllers_basic] get controller again, check name"
   request:
     method: GET
     url: "http://localhost:5000/api/v1/controllers/{returned_id}"
diff --git a/tests/test_get_all.tavern.yaml b/tests/tavern_tests/get_all.tavern.yaml
similarity index 100%
rename from tests/test_get_all.tavern.yaml
rename to tests/tavern_tests/get_all.tavern.yaml
diff --git a/tests/tavern_tests/schedules_basic.tavern.yaml b/tests/tavern_tests/schedules_basic.tavern.yaml
new file mode 100644
index 0000000..0cfe93e
--- /dev/null
+++ b/tests/tavern_tests/schedules_basic.tavern.yaml
@@ -0,0 +1,69 @@
+test_name: Test basic schedule requests
+
+stages:
+- name: "[schedules_basic] Make sure we get any response"
+  request:
+    url: "http://localhost:5000/api/v1/schedules/"
+    method: GET
+  response:
+    status_code: 200
+    verify_response_with:
+        function: validate_schedule:multiple
+
+- name: "[schedules_basic] post schedule, expect it to be echoed back"
+  request:
+    method: POST
+    url: "http://localhost:5000/api/v1/schedules/"
+    json:
+      name: "hello"
+      periods:
+      - start: "00:10"
+        end: "00:20"
+      - start: "00:30"
+        end: "00:40"
+      - start: "00:50"
+        end: "01:00"
+  response:
+    status_code: 201
+    verify_response_with:
+      function: validate_schedule:single
+      function: validate_schedule:check_name
+      extra_kwargs:
+        name: "{tavern.request_vars.json.name}"
+      function: validate_schedule:check_periods
+      extra_kwargs:
+        periods: "{tavern.request_vars.json.periods}"
+    save:
+      json:
+        returned_name: "name"
+        returned_id: "id"
+        returned_periods: "periods"
+
+- name: "[schedules_basic] get schedule, check name and some periods"
+  request:
+    method: GET
+    url: "http://localhost:5000/api/v1/schedules/{returned_id}"
+  response:
+    status_code: 200
+    verify_response_with:
+      function: validate_schedule:single
+      function: validate_schedule:check_name
+      extra_kwargs:
+        name: "{returned_name}"
+      function: validate_schedule:check_periods
+      extra_kwargs:
+        periods: "{returned_periods}"
+
+- name: "[schedules_basic] delete schedule"
+  request:
+    method: DELETE
+    url: "http://localhost:5000/api/v1/schedules/{returned_id}"
+  response:
+    status_code: 200
+
+- name: "[schedules_basic] get deleted schedule, expect 404"
+  request:
+    method: GET
+    url: "http://localhost:5000/api/v1/schedules/{returned_id}"
+  response:
+    status_code: 404
diff --git a/tests/tavern_tests/schedules_protected.tavern.yaml b/tests/tavern_tests/schedules_protected.tavern.yaml
new file mode 100644
index 0000000..dd62064
--- /dev/null
+++ b/tests/tavern_tests/schedules_protected.tavern.yaml
@@ -0,0 +1,72 @@
+test_name: Test protected schedules requests
+
+stages:
+- name: "[schedules_protected] delete protected off schedule; expect forbidden/fail"
+  request:
+    method: DELETE
+    url: "http://localhost:5000/api/v1/schedules/off"
+  response:
+    status_code: 403
+
+- name: "[schedules_protected] get protected off schedule"
+  request:
+    method: GET
+    url: "http://localhost:5000/api/v1/schedules/off"
+  response:
+    status_code: 200
+    verify_response_with:
+      function: validate_schedule:single
+      function: validate_schedule:compare_off
+
+- name: "[schedules_protected] overwrite protected off schedule"
+  request:
+    method: PUT
+    url: "http://localhost:5000/api/v1/schedules/off"
+    json:
+      name: "turned_off"
+      periods:
+        - start: "00:10"
+          end: "00:20"
+  response:
+    status_code: 200
+    verify_response_with:
+      function: validate_schedule:single
+      function: validate_schedule:compare_off
+      function: validate_schedule:check_name
+      extra_kwargs:
+        name: "{tavern.request_vars.json.name}"
+
+- name: "[schedules_protected] delete protected on schedule; expect forbidden/fail"
+  request:
+    method: DELETE
+    url: "http://localhost:5000/api/v1/schedules/on"
+  response:
+    status_code: 403
+
+- name: get protected on schedule
+  request:
+    method: GET
+    url: "http://localhost:5000/api/v1/schedules/on"
+  response:
+    status_code: 200
+    verify_response_with:
+      function: validate_schedule:single
+      function: validate_schedule:compare_on
+
+- name: "[schedules_protected] overwrite protected on schedule"
+  request:
+    method: PUT
+    url: "http://localhost:5000/api/v1/schedules/on"
+    json:
+      name: "turned_on"
+      periods:
+      - start: "16:10"
+        end: "17:20"
+  response:
+    status_code: 200
+    verify_response_with:
+      function: validate_schedule:single
+      function: validate_schedule:compare_on
+      function: validate_schedule:check_name
+      extra_kwargs:
+        name: "{tavern.request_vars.json.name}"
diff --git a/tests/tavern_tests/tags.tavern.yaml b/tests/tavern_tests/tags.tavern.yaml
new file mode 100644
index 0000000..f3576aa
--- /dev/null
+++ b/tests/tavern_tests/tags.tavern.yaml
@@ -0,0 +1,46 @@
+test_name: "[tags] Test tagging of schedules and relays"
+
+stages:
+- name: "[tags] post schedule, expect it to be echoed back by tag"
+  request:
+    method: POST
+    url: "http://localhost:5000/api/v1/schedules/"
+    json:
+      name: "test tagging schedule"
+      periods:
+      - start: "00:50"
+        end: "01:00"
+      tags:
+      - "test_tag_1"
+  response:
+    status_code: 201
+    verify_response_with:
+      function: validate_schedule:single
+      function: validate_schedule:check_name
+      extra_kwargs:
+        name: "{tavern.request_vars.json.name}"
+      function: validate_schedule:check_periods
+      extra_kwargs:
+        periods: "{tavern.request_vars.json.periods}"
+      function: validate_schedule:check_tag
+      extra_kwargs:
+        tag: "{tavern.request_vars.json.tags[0]}"
+    save:
+      json:
+        returned_name: "name"
+        returned_id: "id"
+        returned_periods: "periods"
+
+- name: "[tags] get schedule, check name and some periods"
+  request:
+    method: GET
+    url: "http://localhost:5000/api/v1/schedules/tag/test_tag_1"
+  response:
+    status_code: 200
+    verify_response_with:
+      function: validate_schedule:multiple
+      function: validate_schedule:find
+      extra_kwargs:
+        id: "{returned_id}"
+        name: "{returned_name}"
+        periods: "{returned_periods}"
diff --git a/tests/tavern_utils/__pycache__/validate_schedule.cpython-38.pyc b/tests/tavern_utils/__pycache__/validate_schedule.cpython-38.pyc
new file mode 100644
index 0000000..ca7f6ba
Binary files /dev/null and b/tests/tavern_utils/__pycache__/validate_schedule.cpython-38.pyc differ
diff --git a/tests/tavern_utils/validate_relay.py b/tests/tavern_utils/validate_relay.py
new file mode 100644
index 0000000..0ae475a
--- /dev/null
+++ b/tests/tavern_utils/validate_relay.py
@@ -0,0 +1,2 @@
+def single(response):
+    assert response.json().get("number") >= 0
diff --git a/tests/tavern_utils/validate_schedule.py b/tests/tavern_utils/validate_schedule.py
new file mode 100644
index 0000000..79d1ca6
--- /dev/null
+++ b/tests/tavern_utils/validate_schedule.py
@@ -0,0 +1,96 @@
+import json
+
+def _verify_single(schedule):
+    assert isinstance(schedule.get("id"), str), "schedule ID is not a string"
+    assert isinstance(schedule.get("name"), str), "schedule name is not a string"
+
+    assert isinstance(schedule.get("periods"), list), "schedule periods is not a list"
+    for period in schedule.get("periods"):
+        assert isinstance(period, dict), "schedule periods contain a periods which is not a dict"
+        assert isinstance(period.get("start"), str), "schedule periods contain a periods with start not being a string"
+        assert isinstance(period.get("end"), str), "schedule periods contain a periods with end not being a string"
+
+    assert isinstance(schedule.get("tags"), list), "schedule tags is not a list"
+    for tag in schedule.get("tags"):
+        assert isinstance(tag, str), "schedule tags contain a tag which is not a string"
+
+def single(response):
+    _verify_single(response.json())
+
+def multiple(response):
+    assert isinstance(response.json(), list), "response is not a list"
+    for schedule in response.json():
+        _verify_single(schedule)
+
+def check_name(response, name):
+    assert response.json().get("name") == name, "schedule name check failed"
+
+def check_id(response, id):
+    assert response.json().get("id") == id, "schedule id check failed"
+
+def check_periods(response, periods):
+    periods_json = json.loads(periods.replace("'", "\""))
+    assert len(periods_json) == len(response.json().get("periods")), "periods in response and request have different lengths"
+    for request_period in periods_json:
+        found_in_response = False
+        for response_period in response.json().get("periods"):
+            if response_period.get("start") != request_period.get("start"):
+                continue
+            if response_period.get("end") != request_period.get("end"):
+                continue
+            found_in_response = True
+        if not found_in_response:
+            print(request_period)
+            assert False, "a period from the request was missing from the response"
+
+def check_tag(response, tag):
+    for response_tag in response.json().get("tags"):
+        if response_tag == tag:
+            return
+    assert False, "tag not found in schedule,"
+
+def compare_off(response):
+    assert response.json().get("id") == "off", "schedule off did not return id off"
+    assert len(response.json().get("periods")) == 0, "schedule off has periods"
+
+def compare_on(response):
+    assert response.json().get("id") == "on", "schedule on did not return id on"
+    assert len(response.json().get("periods")) == 1, "schedule on has unexpected amount of periods"
+    assert response.json().get("periods")[0].get("start") == "00:00", "Schedule on has unexpected start"
+    assert response.json().get("periods")[0].get("end") == "23:59", "Schedule on has unexpected start"
+
+def find(response, id=None, name=None, periods=None, tag=None):
+    if periods != None:
+        periods_json = json.loads(periods.replace("'", "\""))
+    for schedule in response.json():
+        if id != None and id != schedule.get("id"):
+            print(schedule.get("id"))
+            continue
+
+        if name != None and name != schedule.get("name"):
+            print(schedule.get("name"))
+            continue
+
+        if periods != None:
+            if len(periods_json) != len(schedule.get("periods")):
+                continue
+            for request_period in periods_json:
+                found_in_response = False
+                for response_period in schedule.get("periods"):
+                    if response_period.get("start") != request_period.get("start"):
+                        continue
+                    if response_period.get("end") != request_period.get("end"):
+                        continue
+                    found_in_response = True
+            if not found_in_response:
+                continue
+
+        if tag != None:
+            found_in_response = False
+            for response_tag in schedule.get("tags"):
+                if response_tag == tag:
+                    found_in_response = True
+            if not found_in_response:
+                continue
+        return
+    assert False, "schedule not found in list"
diff --git a/tests/test_schedules_basic.tavern.yaml b/tests/test_schedules_basic.tavern.yaml
deleted file mode 100644
index f1866c1..0000000
--- a/tests/test_schedules_basic.tavern.yaml
+++ /dev/null
@@ -1,58 +0,0 @@
-test_name: Test basic schedule requests
-
-stages:
-- name: "[test_schedules_basic] Make sure we get any response"
-  request:
-    url: "http://localhost:5000/api/v1/schedules/"
-    method: GET
-  response:
-    status_code: 200
-- name: "[test_schedules_basic] post schedule, expect it to be echoed back"
-  request:
-    method: POST
-    url: "http://localhost:5000/api/v1/schedules/"
-    json:
-      name: "hello"
-      periods:
-      - start: "00:10"
-        end: "00:20"
-      - start: "00:30"
-        end: "00:40"
-      - start: "00:50"
-        end: "01:00"
-  response:
-    status_code: 201
-    json:
-      name: "{tavern.request_vars.json.name}"
-      id: !anystr
-      periods: !anylist
-      tags: !anylist
-    save:
-      json:
-        returned_name: "name"
-        returned_id: "id"
-- name: "[test_schedules_basic] get schedule, check name and some periods"
-  request:
-    method: GET
-    url: "http://localhost:5000/api/v1/schedules/{returned_id}"
-  response:
-    status_code: 200
-    json:
-      name: "{returned_name}"
-      id: !anystr
-      periods: !anylist
-      tags: !anylist
-
-- name: "[test_schedules_basic] delete schedule"
-  request:
-    method: DELETE
-    url: "http://localhost:5000/api/v1/schedules/{returned_id}"
-  response:
-    status_code: 200
-
-- name: "[test_schedules_basic] get deleted schedule, expect 404"
-  request:
-    method: GET
-    url: "http://localhost:5000/api/v1/schedules/{returned_id}"
-  response:
-    status_code: 404
diff --git a/tests/test_schedules_protected.tavern.yaml b/tests/test_schedules_protected.tavern.yaml
deleted file mode 100644
index ae420c3..0000000
--- a/tests/test_schedules_protected.tavern.yaml
+++ /dev/null
@@ -1,78 +0,0 @@
-test_name: Test protected schedules requests
-
-stages:
-- name: "[test_schedules_protected] delete protected off schedule; expect forbidden/fail"
-  request:
-    method: DELETE
-    url: "http://localhost:5000/api/v1/schedules/off"
-  response:
-    status_code: 403
-
-- name: "[test_schedules_protected] get protected off schedule"
-  request:
-    method: GET
-    url: "http://localhost:5000/api/v1/schedules/off"
-  response:
-    status_code: 200
-    json:
-      id: "off"
-      name: "off"
-      periods: []
-      tags: !anylist
-
-- name: "[test_schedules_protected] overwrite protected off schedule"
-  request:
-    method: PUT
-    url: "http://localhost:5000/api/v1/schedules/off"
-    json:
-      name: "turned_off"
-      periods:
-        - start: "00:10"
-          end: "00:20"
-  response:
-    status_code: 200
-    json:
-      id: "off"
-      name: "{tavern.request_vars.json.name}"
-      periods: []
-      tags: !anylist
-
-- name: "[test_schedules_protected] delete protected on schedule; expect forbidden/fail"
-  request:
-    method: DELETE
-    url: "http://localhost:5000/api/v1/schedules/on"
-  response:
-    status_code: 403
-
-- name: get protected on schedule
-  request:
-    method: GET
-    url: "http://localhost:5000/api/v1/schedules/on"
-  response:
-    status_code: 200
-    json:
-      id: "on"
-      name: "on"
-      periods:
-      - start: "00:00"
-        end: "23:59"
-      tags: !anylist
-
-- name: "[test_schedules_protected] overwrite protected on schedule"
-  request:
-    method: PUT
-    url: "http://localhost:5000/api/v1/schedules/on"
-    json:
-      name: "turned_on"
-      periods:
-      - start: "16:10"
-        end: "17:20"
-  response:
-    status_code: 200
-    json:
-      id: "on"
-      name: "{tavern.request_vars.json.name}"
-      periods:
-      - start: "00:00"
-        end: "23:59"
-      tags: !anylist
diff --git a/tests/test_tags.tavern.yaml b/tests/test_tags.tavern.yaml
deleted file mode 100644
index ff2b1ec..0000000
--- a/tests/test_tags.tavern.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-test_name: "[tags] Test tagging of schedules and relays"
-
-stages:
-- name: "[tags] post schedule, expect it to be echoed back by tag"
-  request:
-    method: POST
-    url: "http://localhost:5000/api/v1/schedules/"
-    json:
-      name: "test tagging schedule"
-      periods:
-      - start: "00:50"
-        end: "01:00"
-      tags:
-      - "test_tag_1"
-  response:
-    status_code: 201
-    json:
-      id: !anystr
-      name: "{tavern.request_vars.json.name}"
-      periods:
-      - start: "00:50"
-        end: "01:00"
-      tags:
-        - "{tavern.request_vars.json.tags[0]}"
-    save:
-      json:
-        returned_name: name
-        returned_id: id
-
-- name: "[tags] get schedule, check name and some periods"
-  request:
-    method: GET
-    url: "http://localhost:5000/api/v1/schedules/tag/test_tag_1"
-  response:
-    status_code: 200
-    json:
-    - name: "{returned_name}"
-      id: "{returned_id}"
-      periods:
-      - start: "00:50"
-        end: "01:00"
-      tags:
-      - "test_tag_1"