diff --git a/src/aleph/commands.py b/src/aleph/commands.py index b9c07b19a..174373bf8 100644 --- a/src/aleph/commands.py +++ b/src/aleph/commands.py @@ -33,7 +33,7 @@ from aleph.services import p2p from aleph.services.keys import generate_keypair, save_keys from aleph.services.p2p import singleton -from aleph.web import app, init_cors +from aleph.web import app __author__ = "Moshe Malawach" __copyright__ = "Moshe Malawach" @@ -49,9 +49,6 @@ async def run_server( from aiohttp import web from aleph.web.controllers.listener import broadcast - LOGGER.debug("Initializing CORS") - init_cors() - LOGGER.debug("Setup of runner") app["config"] = config @@ -175,7 +172,6 @@ async def main(args): model.init_db(config, ensure_indexes=True) LOGGER.info("Database initialized.") - init_cors() # FIXME: This is stateful and process-dependent set_start_method("spawn") with Manager() as shared_memory_manager: diff --git a/src/aleph/web/__init__.py b/src/aleph/web/__init__.py index a985c40c9..492f02ae2 100644 --- a/src/aleph/web/__init__.py +++ b/src/aleph/web/__init__.py @@ -8,53 +8,67 @@ import pkg_resources import socketio from aiohttp import web + from aleph.web.controllers import register_routes -app = web.Application(client_max_size=1024 ** 2 * 64) -sio = socketio.AsyncServer(async_mode="aiohttp", cors_allowed_origins="*") -auth = None - -# Configure default CORS settings. -cors = aiohttp_cors.setup( - app, - defaults={ - "*": aiohttp_cors.ResourceOptions( - allow_methods=["GET", "POST"], - allow_credentials=True, - expose_headers="*", - allow_headers="*", - ) - }, -) -sio.attach(app) - -tpl_path = pkg_resources.resource_filename("aleph.web", "templates") -JINJA_LOADER = jinja2.ChoiceLoader( - [ - jinja2.FileSystemLoader(tpl_path), - ] -) -aiohttp_jinja2.setup(app, loader=JINJA_LOADER) -env = aiohttp_jinja2.get_env(app) -env.globals.update( - { - "app": app, - "date": date, - "datetime": datetime, - "time": time, - "timedelta": timedelta, - "int": int, - "float": float, - "len": len, - "pprint": pprint, - } -) - -register_routes(app) - - -def init_cors(): + +def init_cors(app: web.Application): + # Configure default CORS settings. + cors = aiohttp_cors.setup( + app, + defaults={ + "*": aiohttp_cors.ResourceOptions( + allow_methods=["GET", "POST"], + allow_credentials=True, + expose_headers="*", + allow_headers="*", + ) + }, + ) + # Configure CORS on all routes. for route in list(app.router.routes()): if "/socket.io/" not in repr(route.resource): cors.add(route) + + +def init_sio(app: web.Application) -> socketio.AsyncServer: + sio = socketio.AsyncServer(async_mode="aiohttp", cors_allowed_origins="*") + sio.attach(app) + return sio + + +def create_app() -> web.Application: + app = web.Application(client_max_size=1024 ** 2 * 64) + + tpl_path = pkg_resources.resource_filename("aleph.web", "templates") + jinja_loader = jinja2.ChoiceLoader( + [ + jinja2.FileSystemLoader(tpl_path), + ] + ) + aiohttp_jinja2.setup(app, loader=jinja_loader) + env = aiohttp_jinja2.get_env(app) + env.globals.update( + { + "app": app, + "date": date, + "datetime": datetime, + "time": time, + "timedelta": timedelta, + "int": int, + "float": float, + "len": len, + "pprint": pprint, + } + ) + + register_routes(app) + + init_cors(app) + + return app + + +app = create_app() +sio = init_sio(app) diff --git a/tests/api/fixtures/fixture_messages.json b/tests/api/fixtures/fixture_messages.json new file mode 100644 index 000000000..bcbc6cc04 --- /dev/null +++ b/tests/api/fixtures/fixture_messages.json @@ -0,0 +1,227 @@ +[ + { + "chain": "ETH", + "channel": "unit-tests", + "sender": "0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106", + "type": "POST", + "time": 1652126646.5008686, + "item_content": "{\"address\":\"0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106\",\"time\":1652126646.5007327,\"content\":{\"title\":\"My first blog post using Aleph.im\",\"body\":\"Using Aleph.im, we can make a decentralized blog.\"},\"type\":\"test\"}", + "item_hash": "4c33dd1ebf61bbb4342d8258b591fcd52cca73fd7c425542f78311d8f45ba274", + "signature": "0x999ab556b92351e6edf894b4a67f01f0344c7023883eb5bafdf4cd0b98ca91781692ac6b95246c1bf940eedcedfd6dc04751accfbc417ee1b1ae13893634e7eb1c", + "confirmed": false, + "content": { + "address": "0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106", + "time": 1652126646.5007327, + "content": { + "title": "My first blog post using Aleph.im", + "body": "Using Aleph.im, we can make a decentralized blog." + }, + "type": "test" + } + }, + { + "chain": "ETH", + "channel": "unit-tests", + "sender": "0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106", + "type": "STORE", + "time": 1652126721.497669, + "item_content": "{\"address\":\"0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106\",\"time\":1652126721.4974446,\"item_type\":\"storage\",\"item_hash\":\"5ccdd7bccfbc5955e2e40166dd0cdea0b093154fd87bc2bea57e7c768cde2f21\",\"mime_type\":\"text/plain\"}", + "item_hash": "2953f0b52beb79fc0ed1bc455346fdcb530611605e16c636778a0d673d7184af", + "signature": "0xa10129dd561c1bc93e8655daf09520e9f1694989263e25f330b403ad33563f4b64c9ae18f6cbfb33e8a47a095be7a181b140a369e6205fd04eef55397624a7121b", + "content": { + "address": "0x696879aE4F6d8DaDD5b8F1cbb1e663B89b08f106", + "time": 1652126721.4974446, + "item_type": "storage", + "item_hash": "5ccdd7bccfbc5955e2e40166dd0cdea0b093154fd87bc2bea57e7c768cde2f21", + "mime_type": "text/plain" + } + }, + { + "chain": "ETH", + "item_hash": "88a045b90b48c590748a607690fdf85b94b7be4d63940bf4835828b3254b4265", + "sender": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "type": "POST", + "channel": "INTEGRATION_TESTS", + "confirmed": true, + "content": { + "address": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "time": 1648215810.245091, + "type": "POST" + }, + "item_content": "{\"address\":\"0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4\",\"time\":1648215810.245091,\"type\":\"POST\"}", + "item_type": "inline", + "signature": "0x2acfc49c4709a97bb63fd63277304e54b474912785a48efdcd0ee4571f4b42a8730fda00a6c7bdaf20c42f92c8ff5449824a668ab1897359f4a0e503c335b8a21b", + "size": 95, + "time": 1648215810.2451403, + "confirmations": [ + { + "chain": "ETH", + "height": 14474458, + "hash": "0xbb123b07933ce8d1d49c2ba287fb7942f272359a7f220b7fc2b662c65c960ccc" + } + ] + }, + { + "chain": "ETH", + "item_hash": "bc411ae2ba89289458d0168714457e7c9394a29ca83159240585591f4f46444a", + "sender": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "type": "POST", + "channel": "INTEGRATION_TESTS", + "confirmed": true, + "content": { + "address": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "time": 1648215810.1763275, + "type": "POST" + }, + "item_content": "{\"address\":\"0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4\",\"time\":1648215810.1763275,\"type\":\"POST\"}", + "item_type": "inline", + "signature": "0x1fdb9f188490ad82c093dcf44dbc4bae7665b11c85d55b6e2e6d78565a1967de0a7dc03720fe56f340154f8d5d361ba95bca9e40f910f2834f62d2c4416c77a31c", + "size": 96, + "time": 1648215810.1763897, + "confirmations": [ + { + "chain": "ETH", + "height": 14474458, + "hash": "0xbb123b07933ce8d1d49c2ba287fb7942f272359a7f220b7fc2b662c65c960ccc" + } + ] + }, + { + "chain": "ETH", + "item_hash": "3cbb3e09a61429e8ecd21ee392a1543ef79934ef39338d01a6c771b17a46c274", + "sender": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "type": "FORGET", + "channel": "INTEGRATION_TESTS", + "confirmed": true, + "content": { + "address": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "time": 1648215810.1041584, + "hashes": [ + "448b3c6f6455e6f4216b01b43522bddc3564a14c04799ed0ce8af4857c7ba15f" + ], + "reason": "I want to remember this post. Maybe I can forget I forgot it?" + }, + "item_content": "{\"address\":\"0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4\",\"time\":1648215810.1041584,\"hashes\":[\"448b3c6f6455e6f4216b01b43522bddc3564a14c04799ed0ce8af4857c7ba15f\"],\"reason\":\"I want to remember this post. Maybe I can forget I forgot it?\"}", + "item_type": "inline", + "signature": "0x03563d1fbec46358af7ec9d577a57f348dd57a9d7f84b63e5bfb8112a9aca48b120d2913c96a89c98745ffb6f65a4c63c71ae16d017f0ae75c4a5a956182e25a1b", + "size": 233, + "time": 1648215810.104279, + "confirmations": [ + { + "chain": "ETH", + "height": 14474458, + "hash": "0xbb123b07933ce8d1d49c2ba287fb7942f272359a7f220b7fc2b662c65c960ccc" + } + ] + }, + { + "chain": "ETH", + "item_hash": "448b3c6f6455e6f4216b01b43522bddc3564a14c04799ed0ce8af4857c7ba15f", + "sender": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "type": "FORGET", + "channel": "INTEGRATION_TESTS", + "confirmed": true, + "content": { + "address": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "time": 1648215809.0270267, + "hashes": [ + "fea0e00f73102aa951794a3ea85f6f1bbfd3decb804fb73232f2a645a379ae54" + ], + "reason": "This well thought-out content offends me!" + }, + "item_content": "{\"address\":\"0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4\",\"time\":1648215809.0270267,\"hashes\":[\"fea0e00f73102aa951794a3ea85f6f1bbfd3decb804fb73232f2a645a379ae54\"],\"reason\":\"This well thought-out content offends me!\"}", + "item_type": "inline", + "signature": "0x3619c016987c4221c85842ce250f3e50a9b8e42c04d4f9fbdfdfad9941d6c5195a502a4f63289429513bf152d24d0a7bb0533701ec3c7bbca91b18ce7eaa7dee1b", + "size": 213, + "time": 1648215809.027116, + "confirmations": [ + { + "chain": "ETH", + "height": 14474458, + "hash": "0xbb123b07933ce8d1d49c2ba287fb7942f272359a7f220b7fc2b662c65c960ccc" + } + ] + }, + { + "chain": "ETH", + "item_hash": "fea0e00f73102aa951794a3ea85f6f1bbfd3decb804fb73232f2a645a379ae54", + "sender": "0xaC033C1cA5C49Eff98A1D9a56BeDBC4840010BA4", + "type": "POST", + "channel": "INTEGRATION_TESTS", + "confirmed": true, + "content": null, + "item_content": null, + "item_type": "inline", + "signature": "0x1a2bddb50e94e4491dec6b555faf70716d0b18ffa48176857d0d7ca3ff9292be1e619679fda95d50e58250a703c295c02bb5d90833d3345d3238b173f19fea2c1c", + "size": 151, + "time": 1648215808.9630325, + "confirmations": [ + { + "chain": "ETH", + "height": 14474458, + "hash": "0xbb123b07933ce8d1d49c2ba287fb7942f272359a7f220b7fc2b662c65c960ccc" + } + ], + "forgotten_by": [ + "448b3c6f6455e6f4216b01b43522bddc3564a14c04799ed0ce8af4857c7ba15f" + ] + }, + { + "chain": "ETH", + "item_hash": "de4499ec1c0d7e60380d6b538c1ca58220b34224272b8164d144712d5a334097", + "sender": "0x720F319A9c3226dCDd7D8C49163D79EDa1084E98", + "type": "AGGREGATE", + "channel": "aggregate-tests", + "confirmed": true, + "content": { + "address": "0x720F319A9c3226dCDd7D8C49163D79EDa1084E98", + "time": 1648818372.840815, + "key": "test_od", + "content": { + "a": 2, + "b": 3 + } + }, + "item_content": "{\"address\":\"0x720F319A9c3226dCDd7D8C49163D79EDa1084E98\",\"time\":1648818372.840815,\"key\":\"test_od\",\"content\":{\"a\":2,\"b\":3}}", + "item_type": "inline", + "signature": "0xbccfd10ca6da8e2959783a689cb46c77cbe9fe0b3407f18cd7a5bc6ba0da2bae57158b7c837080daa9e62b67ecccb12910297d749e9cec63ea58220c2dcd91221c", + "size": 121, + "time": 1648818372.8409374, + "confirmations": [ + { + "chain": "ETH", + "height": 14500712, + "hash": "0x39e581f14409008fb7d3663c0a24bb5035aff1edbc30d024168ce9426b631e7c" + } + ] + }, + { + "chain": "ETH", + "item_hash": "9200cfab5950e5d173f07d7c61bb0524675d0305e808590e7d0a0752ce65f791", + "sender": "0x720F319A9c3226dCDd7D8C49163D79EDa1084E98", + "type": "AGGREGATE", + "channel": "aggregate-tests", + "confirmed": false, + "content": { + "address": "0x720F319A9c3226dCDd7D8C49163D79EDa1084E98", + "time": 1648818417.7640934, + "key": "test_od", + "content": { + "a": 1, + "b": 23 + } + }, + "item_content": "{\"address\":\"0x720F319A9c3226dCDd7D8C49163D79EDa1084E98\",\"time\":1648818417.7640934,\"key\":\"test_od\",\"content\":{\"a\":1,\"b\":23}}", + "item_type": "inline", + "signature": "0x94e4e6e121c88401b5ea55c2ec4a94ebb98856fe6775fb30b3ac62d183798d7b4323fed7aaead6cbfd0d4e5b1409f15df188ed7f3be969506af40fc0e8d2e8261b", + "size": 123, + "time": 1648818417.7642117, + "confirmations": [ + { + "chain": "ETH", + "height": 14500712, + "hash": "0x39e581f14409008fb7d3663c0a24bb5035aff1edbc30d024168ce9426b631e7c" + } + ] + } +] diff --git a/tests/api/test_messages.py b/tests/api/test_messages.py new file mode 100644 index 000000000..1248752b0 --- /dev/null +++ b/tests/api/test_messages.py @@ -0,0 +1,93 @@ +import itertools +import json +from pathlib import Path +from typing import Dict, Iterable, List + +import pytest +import pytest_asyncio + +from aleph.model.messages import Message +from aleph.web import create_app + +MESSAGES_URI = "/api/v0/messages.json" + + +def get_messages_by_channel(messages: Iterable[Dict], channel: str) -> List[Dict]: + return [msg for msg in messages if msg["channel"] == channel] + + +def assert_messages_equal(messages: Iterable[Dict], expected_messages: Iterable[Dict]): + messages_by_hash = {msg["item_hash"]: msg for msg in messages} + + for expected_message in expected_messages: + message = messages_by_hash[expected_message["item_hash"]] + + assert message["channel"] == expected_message["channel"] + assert message["content"] == expected_message["content"] + assert message["sender"] == expected_message["sender"] + assert message["signature"] == expected_message["signature"] + + +@pytest_asyncio.fixture +async def fixture_messages(test_db): + fixtures_dir = Path(__file__).parent / "fixtures" + fixtures_file = fixtures_dir / "fixture_messages.json" + + with fixtures_file.open() as f: + messages = json.load(f) + + await Message.collection.insert_many(messages) + return messages + + +@pytest.mark.asyncio +async def test_get_messages(fixture_messages, aiohttp_client): + app = create_app() + client = await aiohttp_client(app) + + response = await client.get(MESSAGES_URI) + assert response.status == 200, await response.text() + + data = await response.json() + + messages = data["messages"] + assert len(messages) == len(fixture_messages) + assert_messages_equal(messages, fixture_messages) + + assert data["pagination_total"] == len(messages) + assert data["pagination_page"] == 1 + + +@pytest.mark.asyncio +async def test_get_messages_filter_by_channel(fixture_messages, aiohttp_client): + app = create_app() + client = await aiohttp_client(app) + + async def fetch_messages_by_channel(channel: str) -> Dict: + response = await client.get(MESSAGES_URI, params={"channels": channel}) + assert response.status == 200, await response.text() + return await response.json() + + data = await fetch_messages_by_channel("unit-tests") + messages = data["messages"] + + unit_test_messages = get_messages_by_channel(fixture_messages, "unit-tests") + + assert len(messages) == len(unit_test_messages) + assert_messages_equal(messages, unit_test_messages) + + data = await fetch_messages_by_channel("aggregates-tests") + messages = data["messages"] + + aggregates_test_messages = get_messages_by_channel(fixture_messages, "aggregates-tests") + assert_messages_equal(messages, aggregates_test_messages) + + # Multiple channels + data = await fetch_messages_by_channel("aggregates-tests,unit-tests") + messages = data["messages"] + + assert_messages_equal(messages, itertools.chain(unit_test_messages, aggregates_test_messages)) + + # Nonexistent channel + data = await fetch_messages_by_channel("none-pizza-with-left-beef") + assert data["messages"] == [] diff --git a/tests/api/test_version.py b/tests/api/test_version.py index 4a1d91f73..4c687469e 100644 --- a/tests/api/test_version.py +++ b/tests/api/test_version.py @@ -1,11 +1,12 @@ import pytest -from aleph.web import app +from aleph.web import create_app from aleph import get_git_version @pytest.mark.asyncio async def test_get_version(aiohttp_client): + app = create_app() client = await aiohttp_client(app) response = await client.get("/api/v0/version")