From a1c5a0879088d7b7abd79c3feb9f3fc4aaf4d880 Mon Sep 17 00:00:00 2001
From: Noah Levitt <nlevitt@archive.org>
Date: Thu, 27 Apr 2017 14:34:07 -0700
Subject: [PATCH] raise exception if heartbeat status_info is missing required
 fields

---
 doublethink/services.py |  7 +++++++
 setup.py                |  2 +-
 tests/test_svcreg.py    | 24 ++++++++++++++++++++++++
 3 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/doublethink/services.py b/doublethink/services.py
index f8a04da..7f80415 100644
--- a/doublethink/services.py
+++ b/doublethink/services.py
@@ -59,6 +59,13 @@ class ServiceRegistry(object):
         Returns updated status info on success, un-updated status info on
         failure.
         '''
+        for field in 'role', 'heartbeat_interval', 'load':
+            if not field in status_info:
+                raise Exception(
+                        'status_info is missing required field %s', field)
+        val = status_info['heartbeat_interval']
+        if not (isinstance(val, float) or isinstance(val, int)) or val <= 0:
+            raise Exception('heartbeat_interval must be a number > 0')
         updated_status_info = dict(status_info)
         updated_status_info['last_heartbeat'] = r.now()
         if not 'first_heartbeat' in updated_status_info:
diff --git a/setup.py b/setup.py
index 475ac4d..47f9fc0 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ import codecs
 
 setuptools.setup(
     name='doublethink',
-    version='0.2.0.dev73',
+    version='0.2.0.dev74',
     packages=['doublethink'],
     classifiers=[
         'Programming Language :: Python :: 2.7',
diff --git a/tests/test_svcreg.py b/tests/test_svcreg.py
index 6e8be6d..738b029 100644
--- a/tests/test_svcreg.py
+++ b/tests/test_svcreg.py
@@ -80,6 +80,30 @@ def test_leader_election(rr):
 
 def test_service_registry(rr):
     svcreg = doublethink.ServiceRegistry(rr)
+    # missing required fields
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({})
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"role":"foo","load":1})
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"role":"foo","heartbeat_interval":1.0})
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"heartbeat_interval":1.0,"load":1})
+
+    # invalid heartbeat interval (we accept anything for load and role)
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"heartbeat_interval":-1,"role":"foo","load":1})
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"heartbeat_interval":"strang","role":"foo","load":1})
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"heartbeat_interval":[],"role":"foo","load":1})
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"heartbeat_interval":[1],"role":"foo","load":1})
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"heartbeat_interval":{},"role":"foo","load":1})
+    with pytest.raises(Exception) as excinfo:
+        svcreg.heartbeat({"heartbeat_interval":{1:2},"role":"foo","load":1})
+
     assert svcreg.available_service("yes-such-role") == None
     assert svcreg.available_services("yes-such-role") == []
     assert svcreg.available_services() == []