Skip to content

Commit

Permalink
feat: cleaner GET interface for KERI verifier service (#546)
Browse files Browse the repository at this point in the history
* feat: make GET interface more RESTy

* test: unit tests for key state update endpoints
  • Loading branch information
iFergal authored Jul 8, 2024
1 parent fbd239d commit df2ba12
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 23 deletions.
32 changes: 19 additions & 13 deletions backend-services/keri-ballot-verifier/src/verifier/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ def __init__(self, hby):
self.hby = hby

def on_get(self, req, resp):
oobi = getRequiredParam(req.get_media(), 'oobi')
# This should be a path param but is causing issues, query will do.
oobi = req.params.get('url')
if oobi is None or oobi == "":
raise falcon.HTTPBadRequest(description=f"required field url missing from request")

result = self.hby.db.roobi.get(keys=(oobi,))
if result:
Expand All @@ -47,7 +50,7 @@ def on_post(self, req, resp):
resp.status = falcon.HTTP_202


class KeyStateEnd:
class KeyStateCreateEnd:
def __init__(self, hby, hab, queries):
self.hby = hby
self.hab = hab
Expand All @@ -67,24 +70,27 @@ def on_post(self, req, resp):
for (keys, saider) in self.hby.db.knas.getItemIter(keys=(pre,)):
self.hby.db.knas.rem(keys)
self.hby.db.ksns.rem((saider.qb64,))
self.hby.db.ksns.rem((saider.qb64,))
self.hby.db.kdts.rem((saider.qb64,))

self.queries.append(pre)
resp.status = falcon.HTTP_202

def on_get(self, req, resp):
body = req.get_media()
aid = getRequiredParam(body, 'pre') # TODO this should be a query param

class KeyStateQueryEnd:
def __init__(self, hby, hab):
self.hby = hby
self.hab = hab

def on_get(self, _, resp, pre):
try:
kever: eventing.Kever = self.hab.kevers[aid]
kever: eventing.Kever = self.hab.kevers[pre]
except KeyError as _:
resp.status = falcon.HTTP_404
resp.text = f"Unknown AID {aid}"
resp.text = f"Unknown AID {pre}"
return

ksn = None
for (_, saider) in self.hby.db.knas.getItemIter(keys=(aid,)):
for (_, saider) in self.hby.db.knas.getItemIter(keys=(pre,)):
ksn = self.hby.db.ksns.get(keys=(saider.qb64,))
break

Expand All @@ -102,15 +108,15 @@ def __init__(self, hab):

def on_post(self, req, resp):
body = req.get_media()
aid = getRequiredParam(body, 'aid')
pre = getRequiredParam(body, 'pre')
signature = getRequiredParam(body, 'signature')
payload = getRequiredParam(body, 'payload')

try:
kever = self.hab.kevers[aid]
kever = self.hab.kevers[pre]
except KeyError as _:
resp.status = falcon.HTTP_404
resp.text = f"Unknown AID {aid}, please ensure corresponding OOBI has been resolved"
resp.text = f"Unknown AID {pre}, please ensure corresponding OOBI has been resolved"
return
verfers = kever.verfers

Expand All @@ -132,7 +138,7 @@ def on_post(self, req, resp):


class HealthEnd:
def on_get(self, req, resp):
def on_get(self, _, resp):
resp.status = falcon.HTTP_OK
resp.media = {"message": f"Health is okay. Time is {nowIso8601()}"}

Expand Down
5 changes: 4 additions & 1 deletion backend-services/keri-ballot-verifier/src/verifier/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,19 @@ def setupVerifier(hby, hab, name, port, adminPort):

return doers


def createAdminApp(hby, hab, queries):
# Set up Falcon administrative application
adminApp = falcon.App(cors_enable=True)
adminApp.add_route("/oobi", controllers.OOBIEnd(hby=hby))
adminApp.add_route("/keystate", controllers.KeyStateEnd(hby=hby, hab=hab, queries=queries))
adminApp.add_route("/keystate", controllers.KeyStateCreateEnd(hby=hby, hab=hab, queries=queries))
adminApp.add_route("/keystate/{pre}", controllers.KeyStateQueryEnd(hby=hby, hab=hab))
adminApp.add_route("/verify", controllers.VerificationEnd(hab=hab))
adminApp.add_route("/health", controllers.HealthEnd())

return adminApp


class Querier(doing.DoDoer):

def __init__(self, hby, hab, queries, kvy):
Expand Down
129 changes: 120 additions & 9 deletions backend-services/keri-ballot-verifier/tests/test_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import falcon
import pytest
from falcon import testing
from keri import kering
from keri import kering, core
from hio.help import decking
from types import SimpleNamespace

from verifier.verify import createAdminApp

Expand All @@ -13,7 +14,7 @@
}

verify_params = {
"aid": "sample_aid",
"pre": "sample_aid",
"signature": "sample_signature",
"payload": "sample_payload"
}
Expand All @@ -22,65 +23,79 @@
"invalid_param": "sample"
}

ksn_params = {
"pre": "sample_aid"
}

response_no_params = "empty JSON body"
response_missing_params = ["required field", "missing from request"]

queries = decking.Deck()


@pytest.fixture
def mock_hby():
# Mock the hby object
mock = MagicMock()
mock.db.roobi.get.return_value = None
mock.db.oobis.put.return_value = None
mock.db.knas.getItemIter.return_value = None
mock.db.knas.rem.return_value = None
mock.db.ksns.rem.return_value = None
mock.db.kdts.rem.return_value = None
return mock


@pytest.fixture
def mock_hab():
# Mock the hab object
mock = MagicMock()
mock.kevers = {}
return mock


@pytest.fixture
def client(mock_hby, mock_hab):
adminApp = createAdminApp(mock_hby, mock_hab, queries)
return testing.TestClient(adminApp)


class TestOOBIEnd:
def test_get_oobi(self, client, mock_hby):
mock_get = MagicMock()
mock_get.return_value = MagicMock(cid="sample_cid")
mock_hby.db.roobi.get = mock_get

response = client.simulate_get('/oobi', json=oobi_params)
response = client.simulate_get('/oobi?url=sample_oobi')

assert response.status == falcon.HTTP_200
assert response.text == 'sample_cid'

def test_get_oobi_not_found(self, client, mock_hby):
mock_get = MagicMock()
mock_get.return_value = None
mock_hby.hby.db.roobi.get = mock_get
mock_hby.db.roobi.get = mock_get

response = client.simulate_get('/oobi', json=oobi_params)
response = client.simulate_get('/oobi?url=sample_oobi_not_found')

assert response.status == falcon.HTTP_404

def test_get_oobi_no_params(self, client):
response = client.simulate_get('/oobi')

assert response.status == falcon.HTTP_400
assert response_no_params in response.text
for substring in response_missing_params:
assert substring in response.text

def test_get_oobi_empty_params(self, client):
response = client.simulate_get('/oobi', json={})
response = client.simulate_get('/oobi?url=')

assert response.status == falcon.HTTP_400
for substring in response_missing_params:
assert substring in response.text

def test_get_oobi_invalid_params(self, client):
response = client.simulate_get('/oobi', json=invalid_params)
response = client.simulate_get('/oobi?oobiurl=sampleurl')

assert response.status == falcon.HTTP_400
for substring in response_missing_params:
Expand Down Expand Up @@ -121,6 +136,7 @@ def test_post_oobi_invalid_params(self, client):
for substring in response_missing_params:
assert substring in response.text


class TestVerificationEnd:
def test_post_verify_success(self, client, mock_hab):
mock_kever = MagicMock()
Expand Down Expand Up @@ -186,4 +202,99 @@ def test_post_verify_invalid_params(self, client):

assert response.status == falcon.HTTP_400
for substring in response_missing_params:
assert substring in response.text
assert substring in response.text


class TestKeyStateCreateEnd:
def test_post_ks_success(self, client, mock_hby, mock_hab):
mock_kever = MagicMock()
mock_hab.kevers = {"sample_aid": mock_kever}

keys = ('sample_aid', 'EDioKSZWeNZE-kkW0lVZ5eKXOXGrfDDfuyI99HSaMZTU')
saider = core.coring.Saider(qb64='ED3JCRbzw2KkY-w-Z81bqhu6U3OZ7Dq5szt5mX4bmnUI')

mock_get = MagicMock()
mock_get.return_value = [(keys, saider)]
mock_hby.db.knas.getItemIter = mock_get

response = client.simulate_post('/keystate', json=ksn_params)
assert response.status == falcon.HTTP_202
assert mock_hby.db.knas.rem.call_args.args[0] == keys
assert mock_hby.db.ksns.rem.call_args.args[0] == (saider.qb64,)
assert mock_hby.db.kdts.rem.call_args.args[0] == (saider.qb64,)
assert queries.popleft() == "sample_aid"

def test_post_ks_unknown_aid(self, client, mock_hab):
mock_kever = MagicMock()
mock_hab.kevers = {"different_aid": mock_kever}
response = client.simulate_post('/keystate', json=ksn_params)
assert response.status == falcon.HTTP_404
assert "Unknown AID" in response.text

def test_post_ks_no_params(self, client):
response = client.simulate_post('/keystate')
assert response.status == falcon.HTTP_400
assert response_no_params in response.text

def test_post_ks_empty_params(self, client):
response = client.simulate_post('/keystate', json={})

assert response.status == falcon.HTTP_400
for substring in response_missing_params:
assert substring in response.text

def test_post_ks_invalid_params(self, client):
response = client.simulate_post('/keystate', json=invalid_params)

assert response.status == falcon.HTTP_400
for substring in response_missing_params:
assert substring in response.text


class TestKeyStateQueryEnd:
def test_get_ks_complete(self, client, mock_hby, mock_hab):
mock_kever = MagicMock()
mock_kever.serder.said = "sample_aid"
mock_hab.kevers = {"sample_aid": mock_kever}

keys = ('sample_aid', 'EDioKSZWeNZE-kkW0lVZ5eKXOXGrfDDfuyI99HSaMZTU')
saider = core.coring.Saider(qb64='ED3JCRbzw2KkY-w-Z81bqhu6U3OZ7Dq5szt5mX4bmnUI')

mock_get_knas = MagicMock()
mock_get_knas.return_value = [(keys, saider)]
mock_hby.db.knas.getItemIter = mock_get_knas

mock_get_ksns = MagicMock()
mock_get_ksns.return_value = SimpleNamespace(**({"d": "sample_aid"})) # Allows dot notation
mock_hby.db.ksns.get = mock_get_ksns

response = client.simulate_get('/keystate/sample_aid')
assert response.status == falcon.HTTP_200
assert "true" in response.text

def test_get_ks_not_complete(self, client, mock_hby, mock_hab):
mock_kever = MagicMock()
mock_kever.serder.said = "sample_aid"
mock_hab.kevers = {"sample_aid": mock_kever}

keys = ('sample_aid', 'EDioKSZWeNZE-kkW0lVZ5eKXOXGrfDDfuyI99HSaMZTU')
saider = core.coring.Saider(qb64='ED3JCRbzw2KkY-w-Z81bqhu6U3OZ7Dq5szt5mX4bmnUI')

mock_get_knas = MagicMock()
mock_get_knas.return_value = [(keys, saider)]
mock_hby.db.knas.getItemIter = mock_get_knas

mock_get_ksns = MagicMock()
mock_get_ksns.return_value = None
mock_hby.db.ksns.get = mock_get_ksns

response = client.simulate_get('/keystate/sample_aid')
assert response.status == falcon.HTTP_200
assert "false" in response.text

def test_get_ks_unknown_aid(self, client, mock_hab):
mock_kever = MagicMock()
mock_hab.kevers = {"different_aid": mock_kever}
response = client.simulate_get('/keystate/sample_aid')
assert response.status == falcon.HTTP_404
assert "Unknown AID" in response.text

0 comments on commit df2ba12

Please sign in to comment.