diff --git a/tests/executor/test_integration.py b/tests/executor/test_integration.py index 2f39ada..2071767 100644 --- a/tests/executor/test_integration.py +++ b/tests/executor/test_integration.py @@ -1,9 +1,10 @@ import datetime as dt from hashlib import sha256 -from typing import Dict, Any, Union +from typing import Dict, Any, Union, Tuple import aiohttp import pytest +import pytest_asyncio from aleph.sdk import AlephClient from aleph_message.models import ( ItemType, @@ -74,6 +75,21 @@ def mock_vrf_request() -> VRFRequest: return vrf_request +@pytest_asyncio.fixture +async def published_vrf_request( + mock_ccn_client: aiohttp.ClientSession, mock_vrf_request: VRFRequest +) -> Tuple[MessageDict, VRFRequest]: + sender = "aleph_vrf_coordinator" + message_dict = make_post_message(mock_vrf_request, sender=sender) + + resp = await mock_ccn_client.post( + "/api/v0/messages", + json={"message": message_dict, "sync": True}, + ) + assert resp.status == 200, await resp.text() + return message_dict, mock_vrf_request + + def assert_vrf_hash_matches_request( response_hash: VRFResponseHash, vrf_request: VRFRequest, request_item_hash: ItemHash ): @@ -163,7 +179,7 @@ async def assert_aleph_message_matches_random_bytes( async def test_normal_request_flow( mock_ccn_client: aiohttp.ClientSession, executor_client: aiohttp.ClientSession, - mock_vrf_request: VRFRequest, + published_vrf_request: Tuple[MessageDict, VRFRequest], ): """ Test that the executor works under normal circumstances: @@ -172,15 +188,8 @@ async def test_normal_request_flow( 3. The coordinator calls /publish. """ - sender = "aleph_vrf_coordinator" - message_dict = make_post_message(mock_vrf_request, sender=sender) - item_hash = ItemHash(message_dict["item_hash"]) - - resp = await mock_ccn_client.post( - "/api/v0/messages", - json={"message": message_dict, "sync": True}, - ) - assert resp.status == 200, await resp.text() + message_dict, vrf_request = published_vrf_request + item_hash = message_dict["item_hash"] resp = await executor_client.post(f"/generate/{item_hash}") assert resp.status == 200, await resp.text() @@ -188,7 +197,7 @@ async def test_normal_request_flow( response_hash = VRFResponseHash.parse_obj(response_json["data"]) - assert_vrf_hash_matches_request(response_hash, mock_vrf_request, item_hash) + assert_vrf_hash_matches_request(response_hash, vrf_request, item_hash) random_hash_message = await assert_aleph_message_matches_response_hash( mock_ccn_client._base_url, response_hash ) @@ -201,7 +210,7 @@ async def test_normal_request_flow( assert_random_number_matches_request( random_bytes=random_bytes, response_hash=response_hash, - vrf_request=mock_vrf_request, + vrf_request=vrf_request, ) random_number_message = await assert_aleph_message_matches_random_bytes( mock_ccn_client._base_url, random_bytes @@ -210,3 +219,74 @@ async def test_normal_request_flow( # Sanity checks on the message assert random_number_message.sender == random_hash_message.sender assert random_number_message.chain == random_hash_message.chain + + +@pytest.mark.asyncio +async def test_call_publish_before_generate( + executor_client: aiohttp.ClientSession, + published_vrf_request: Tuple[MessageDict, VRFRequest], +): + """ + Test that calling /publish before /generate does not leak any data. + """ + + # Create the coordinator request + message_dict, vrf_request = published_vrf_request + item_hash = message_dict["item_hash"] + + # Use the item_hash of an existing POST message, just for fun + resp = await executor_client.post(f"/publish/{item_hash}") + assert resp.status == 404 + + +@pytest.mark.asyncio +async def test_call_publish_twice( + mock_ccn_client: aiohttp.ClientSession, + executor_client: aiohttp.ClientSession, + published_vrf_request: Tuple[MessageDict, VRFRequest], +): + """ + Test that the executor will only return data the first time POST /publish is called. + This feature makes it possible for the coordinator to detect that someone else already received + the generated random number. + """ + message_dict, vrf_request = published_vrf_request + item_hash = message_dict["item_hash"] + + resp = await executor_client.post(f"/generate/{item_hash}") + assert resp.status == 200, await resp.text() + response_json = await resp.json() + + response_hash = VRFResponseHash.parse_obj(response_json["data"]) + + # Call POST /publish a first time + resp = await executor_client.post(f"/publish/{response_hash.message_hash}") + assert resp.status == 200, await resp.text() + + # Call it a second time + resp = await executor_client.post(f"/publish/{response_hash.message_hash}") + assert resp.status == 404, await resp.text() + + +@pytest.mark.asyncio +async def test_call_generate_twice( + mock_ccn_client: aiohttp.ClientSession, + executor_client: aiohttp.ClientSession, + published_vrf_request: Tuple[MessageDict, VRFRequest], +): + """ + Test that calling POST /generate twice with the same request does not generate a new random number. + """ + ... + + +@pytest.mark.asyncio +async def test_call_generate_without_aleph_message( + mock_ccn_client: aiohttp.ClientSession, + executor_client: aiohttp.ClientSession, + mock_vrf_request: VRFRequest, +): + """ + Test that calling POST /generate without an aleph message fails. + """ + ...