From 7fb18ebf7f90fe064cf6b81872375b2db151ba85 Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Tue, 5 Aug 2025 12:42:55 +0300 Subject: [PATCH 01/18] CAIP-80 add basic POC test --- tests/bdk/integration/__init__.py | 0 tests/bdk/integration/test_post_message.py | 56 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/bdk/integration/__init__.py create mode 100644 tests/bdk/integration/test_post_message.py diff --git a/tests/bdk/integration/__init__.py b/tests/bdk/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bdk/integration/test_post_message.py b/tests/bdk/integration/test_post_message.py new file mode 100644 index 00000000..df83fdae --- /dev/null +++ b/tests/bdk/integration/test_post_message.py @@ -0,0 +1,56 @@ +import os +import yaml +from pytest import fixture, mark +import re +from datetime import datetime, timedelta + +from symphony.bdk.core.config.loader import BdkConfigLoader +from symphony.bdk.core.symphony_bdk import SymphonyBdk + + + +NUMBER_OF_MESSAGES = 10 +STREAM_ID = os.getenv("STREAM_ID", "put-stream-id-to-env-vars") +CONFIG_PATH = "./config.yaml" + +@fixture +def bot_config(): + bot_user = os.getenv("BOT_USERNAME", "put-useranme-to-env-vars") + sym_host = os.getenv("SYMPHONY_HOST", "put-symphony-host-to-env-vars") + key_path = "./key.pem" + bot_config = {"host": sym_host, + "bot": {"username": bot_user, "privateKey": {"path": key_path}}} + with open(CONFIG_PATH, "w") as config_file: + yaml.dump(bot_config, config_file) + + with open(key_path, "w") as key_file: + key_file.write(os.getenv("TEST_RSA_KEY", "PUT A KEY HERE OR INTO ENV VAR")) + yield + os.remove(key_path), os.remove(CONFIG_PATH) + +async def send_messages(messages, stream_id, since): + for i in range(NUMBER_OF_MESSAGES): + await messages.send_message(stream_id, f"{i}-{since}") + +async def get_test_messages(bdk, since): + messages = await bdk.messages().list_messages(STREAM_ID, since=since) + cleaned_messages_text = [ + re.sub(r"<[^>]+>", " ", msg["message"]).strip() + for msg in messages + ] + return cleaned_messages_text + + +@mark.asyncio +async def test_bot_read_write_messages(bot_config): + # Given: test execution start time + since = int((datetime.now() - timedelta(seconds=2)).timestamp()) * 1000 + # Given: BDK is initialized with config + config = BdkConfigLoader.load_from_symphony_dir(CONFIG_PATH) + async with SymphonyBdk(config) as bdk: + # When: messages are sent via bot + await send_messages(bdk.messages(), STREAM_ID, since) + # Then: messages are readable with the same bot + messages = await get_test_messages(bdk, since) + # Then: Expected messages are posted to the room + assert sorted(messages) == [f"{i}-{since}" for i in range(NUMBER_OF_MESSAGES)] \ No newline at end of file From a2be0eda06d61147e11309ce94bc28e52d1cad1b Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Tue, 5 Aug 2025 12:47:13 +0300 Subject: [PATCH 02/18] CAIP-80 test updated path --- tests/bdk/integration/test_post_message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bdk/integration/test_post_message.py b/tests/bdk/integration/test_post_message.py index df83fdae..0dc6d40a 100644 --- a/tests/bdk/integration/test_post_message.py +++ b/tests/bdk/integration/test_post_message.py @@ -11,13 +11,13 @@ NUMBER_OF_MESSAGES = 10 STREAM_ID = os.getenv("STREAM_ID", "put-stream-id-to-env-vars") -CONFIG_PATH = "./config.yaml" +CONFIG_PATH = "/home/runner/.symphony/config.yaml" @fixture def bot_config(): bot_user = os.getenv("BOT_USERNAME", "put-useranme-to-env-vars") sym_host = os.getenv("SYMPHONY_HOST", "put-symphony-host-to-env-vars") - key_path = "./key.pem" + key_path = "/home/runner/.symphony/key.pem" bot_config = {"host": sym_host, "bot": {"username": bot_user, "privateKey": {"path": key_path}}} with open(CONFIG_PATH, "w") as config_file: From 2a312e7f8c8646595ad38814e82a2604e854efbe Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Tue, 5 Aug 2025 17:18:20 +0300 Subject: [PATCH 03/18] CAIP-80 update test --- tests/bdk/integration/test_post_message.py | 61 ++++++++++++++-------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/tests/bdk/integration/test_post_message.py b/tests/bdk/integration/test_post_message.py index 0dc6d40a..dbd31c53 100644 --- a/tests/bdk/integration/test_post_message.py +++ b/tests/bdk/integration/test_post_message.py @@ -1,32 +1,49 @@ import os -import yaml -from pytest import fixture, mark import re from datetime import datetime, timedelta +from pathlib import Path + +import pytest +import yaml from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.symphony_bdk import SymphonyBdk +STREAM_ID = os.getenv("STREAM_ID") +BOT_USERNAME = os.getenv("BOT_USERNAME") +SYMPHONY_HOST = os.getenv("SYMPHONY_HOST") +TEST_RSA_KEY = os.getenv("TEST_RSA_KEY") +# Skip all tests in this file if the required environment variables are not set. +pytestmark = pytest.mark.skipif( + not all([STREAM_ID, BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY]), + reason="Required environment variables for integration tests are not set " + "(STREAM_ID, BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY)" +) NUMBER_OF_MESSAGES = 10 -STREAM_ID = os.getenv("STREAM_ID", "put-stream-id-to-env-vars") -CONFIG_PATH = "/home/runner/.symphony/config.yaml" - -@fixture -def bot_config(): - bot_user = os.getenv("BOT_USERNAME", "put-useranme-to-env-vars") - sym_host = os.getenv("SYMPHONY_HOST", "put-symphony-host-to-env-vars") - key_path = "/home/runner/.symphony/key.pem" - bot_config = {"host": sym_host, - "bot": {"username": bot_user, "privateKey": {"path": key_path}}} - with open(CONFIG_PATH, "w") as config_file: - yaml.dump(bot_config, config_file) - - with open(key_path, "w") as key_file: - key_file.write(os.getenv("TEST_RSA_KEY", "PUT A KEY HERE OR INTO ENV VAR")) - yield - os.remove(key_path), os.remove(CONFIG_PATH) +@pytest.fixture +def bot_config_path(tmp_path: Path): + key_path = tmp_path / "key.pem" + config_path = tmp_path / "config.yaml" + + + bot_config_dict = { + "host": SYMPHONY_HOST, + "bot": { + "username": BOT_USERNAME, + "privateKey": {"path": str(key_path)} + } + } + + # Write the key and config files + key_path.write_text(TEST_RSA_KEY) + with config_path.open("w") as config_file: + yaml.dump(bot_config_dict, config_file) + + yield config_path + + os.remove(key_path), os.remove(config_path) async def send_messages(messages, stream_id, since): for i in range(NUMBER_OF_MESSAGES): @@ -41,12 +58,12 @@ async def get_test_messages(bdk, since): return cleaned_messages_text -@mark.asyncio -async def test_bot_read_write_messages(bot_config): +@pytest.mark.asyncio +async def test_bot_read_write_messages(bot_config_path): # Given: test execution start time since = int((datetime.now() - timedelta(seconds=2)).timestamp()) * 1000 # Given: BDK is initialized with config - config = BdkConfigLoader.load_from_symphony_dir(CONFIG_PATH) + config = BdkConfigLoader.load_from_symphony_dir(str(bot_config_path)) async with SymphonyBdk(config) as bdk: # When: messages are sent via bot await send_messages(bdk.messages(), STREAM_ID, since) From 48d2f2b39c8bda07a7047621d0ba0c98964bd0dc Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Tue, 5 Aug 2025 18:32:22 +0300 Subject: [PATCH 04/18] CAIP-80 propagate secret and env vars --- .github/workflows/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 581015ff..eed165ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,11 @@ jobs: run: poetry install - name: Run tests with coverage + env: + TEST_RSA_KEY: ${{ secrets.TEST_RSA_KEY }} + BOT_USERNAME: ${{ vars.BOT_USERNAME }} + STREAM_ID: ${{ vars.STREAM_ID }} + SYMPHONY_HOST: ${{ vars.SYMPHONY_HOST }} run: poetry run pytest timeout-minutes: 10 From ecdd16b63fa9313cd0d50eba846bd7c9c567b3e7 Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Tue, 5 Aug 2025 18:38:52 +0300 Subject: [PATCH 05/18] CAIP-80 add uuid to test to differentiate test output --- tests/bdk/integration/test_post_message.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/bdk/integration/test_post_message.py b/tests/bdk/integration/test_post_message.py index dbd31c53..0a685454 100644 --- a/tests/bdk/integration/test_post_message.py +++ b/tests/bdk/integration/test_post_message.py @@ -2,6 +2,7 @@ import re from datetime import datetime, timedelta from pathlib import Path +from uuid import uuid4 import pytest import yaml @@ -21,7 +22,7 @@ "(STREAM_ID, BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY)" ) -NUMBER_OF_MESSAGES = 10 +NUMBER_OF_MESSAGES = 3 @pytest.fixture def bot_config_path(tmp_path: Path): key_path = tmp_path / "key.pem" @@ -45,29 +46,30 @@ def bot_config_path(tmp_path: Path): os.remove(key_path), os.remove(config_path) -async def send_messages(messages, stream_id, since): +async def send_messages(messages, stream_id, since, uuid): for i in range(NUMBER_OF_MESSAGES): - await messages.send_message(stream_id, f"{i}-{since}") + await messages.send_message(stream_id, f"{uuid}-{i}-{since}") -async def get_test_messages(bdk, since): +async def get_test_messages(bdk, since, uuid): messages = await bdk.messages().list_messages(STREAM_ID, since=since) cleaned_messages_text = [ re.sub(r"<[^>]+>", " ", msg["message"]).strip() for msg in messages ] - return cleaned_messages_text + return list(filter(lambda msg: msg.startswith(uuid), cleaned_messages_text, )) @pytest.mark.asyncio async def test_bot_read_write_messages(bot_config_path): + uuid = str(uuid4()) # Given: test execution start time since = int((datetime.now() - timedelta(seconds=2)).timestamp()) * 1000 # Given: BDK is initialized with config config = BdkConfigLoader.load_from_symphony_dir(str(bot_config_path)) async with SymphonyBdk(config) as bdk: # When: messages are sent via bot - await send_messages(bdk.messages(), STREAM_ID, since) + await send_messages(bdk.messages(), STREAM_ID, since, uuid) # Then: messages are readable with the same bot - messages = await get_test_messages(bdk, since) + messages = await get_test_messages(bdk, since, uuid) # Then: Expected messages are posted to the room - assert sorted(messages) == [f"{i}-{since}" for i in range(NUMBER_OF_MESSAGES)] \ No newline at end of file + assert sorted(messages) == [f"{uuid}-{i}-{since}" for i in range(NUMBER_OF_MESSAGES)] \ No newline at end of file From 03a01a00ba714162e51e5e16221bd9cc8022969a Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Tue, 5 Aug 2025 18:42:28 +0300 Subject: [PATCH 06/18] CAIP-80 update formatting --- tests/bdk/integration/test_post_message.py | 30 ++++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/bdk/integration/test_post_message.py b/tests/bdk/integration/test_post_message.py index 0a685454..1d10a33f 100644 --- a/tests/bdk/integration/test_post_message.py +++ b/tests/bdk/integration/test_post_message.py @@ -19,22 +19,20 @@ pytestmark = pytest.mark.skipif( not all([STREAM_ID, BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY]), reason="Required environment variables for integration tests are not set " - "(STREAM_ID, BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY)" + "(STREAM_ID, BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY)", ) NUMBER_OF_MESSAGES = 3 + + @pytest.fixture def bot_config_path(tmp_path: Path): key_path = tmp_path / "key.pem" config_path = tmp_path / "config.yaml" - bot_config_dict = { "host": SYMPHONY_HOST, - "bot": { - "username": BOT_USERNAME, - "privateKey": {"path": str(key_path)} - } + "bot": {"username": BOT_USERNAME, "privateKey": {"path": str(key_path)}}, } # Write the key and config files @@ -46,17 +44,25 @@ def bot_config_path(tmp_path: Path): os.remove(key_path), os.remove(config_path) + async def send_messages(messages, stream_id, since, uuid): for i in range(NUMBER_OF_MESSAGES): - await messages.send_message(stream_id, f"{uuid}-{i}-{since}") + await messages.send_message( + stream_id, f"{uuid}-{i}-{since}" + ) + async def get_test_messages(bdk, since, uuid): messages = await bdk.messages().list_messages(STREAM_ID, since=since) cleaned_messages_text = [ - re.sub(r"<[^>]+>", " ", msg["message"]).strip() - for msg in messages + re.sub(r"<[^>]+>", " ", msg["message"]).strip() for msg in messages ] - return list(filter(lambda msg: msg.startswith(uuid), cleaned_messages_text, )) + return list( + filter( + lambda msg: msg.startswith(uuid), + cleaned_messages_text, + ) + ) @pytest.mark.asyncio @@ -72,4 +78,6 @@ async def test_bot_read_write_messages(bot_config_path): # Then: messages are readable with the same bot messages = await get_test_messages(bdk, since, uuid) # Then: Expected messages are posted to the room - assert sorted(messages) == [f"{uuid}-{i}-{since}" for i in range(NUMBER_OF_MESSAGES)] \ No newline at end of file + assert sorted(messages) == [ + f"{uuid}-{i}-{since}" for i in range(NUMBER_OF_MESSAGES) + ] From c3e013174cd7fa38da6d704b84f78d0f6ce5572b Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Wed, 6 Aug 2025 10:10:24 +0300 Subject: [PATCH 07/18] CAIP-80 test commit --- tests/bdk/integration/test_post_message.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bdk/integration/test_post_message.py b/tests/bdk/integration/test_post_message.py index 1d10a33f..d26a21ae 100644 --- a/tests/bdk/integration/test_post_message.py +++ b/tests/bdk/integration/test_post_message.py @@ -65,6 +65,7 @@ async def get_test_messages(bdk, since, uuid): ) + @pytest.mark.asyncio async def test_bot_read_write_messages(bot_config_path): uuid = str(uuid4()) From 454feb68b01fa8ce94797951665f19e232e5b37d Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Thu, 7 Aug 2025 22:59:13 +0300 Subject: [PATCH 08/18] CAIP-80 feed test is added --- .github/workflows/build.yml | 5 +- tests/bdk/integration/e2e_test.py | 116 +++++++++++++++++++++ tests/bdk/integration/helpers.py | 91 ++++++++++++++++ tests/bdk/integration/test_post_message.py | 84 --------------- 4 files changed, 211 insertions(+), 85 deletions(-) create mode 100644 tests/bdk/integration/e2e_test.py create mode 100644 tests/bdk/integration/helpers.py delete mode 100644 tests/bdk/integration/test_post_message.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eed165ed..3d3a3330 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,9 +56,12 @@ jobs: - name: Run tests with coverage env: TEST_RSA_KEY: ${{ secrets.TEST_RSA_KEY }} - BOT_USERNAME: ${{ vars.BOT_USERNAME }} + MSG_BOT_USERNAME: ${{ vars.MSG_BOT_USERNAME }} + FEED_BOT_USERNAME: ${{ vars.FEED_BOT_USERNAME }} STREAM_ID: ${{ vars.STREAM_ID }} SYMPHONY_HOST: ${{ vars.SYMPHONY_HOST }} + TEST_USER_ID: ${{ vars.TEST_SYM_USER_ID }} + BOT_USER_ID: ${{ vars.BOT_USER_ID }} run: poetry run pytest timeout-minutes: 10 diff --git a/tests/bdk/integration/e2e_test.py b/tests/bdk/integration/e2e_test.py new file mode 100644 index 00000000..cd8dc436 --- /dev/null +++ b/tests/bdk/integration/e2e_test.py @@ -0,0 +1,116 @@ +import asyncio +from datetime import datetime, timedelta +from uuid import uuid4 + +import pytest +import pytest_asyncio + +from symphony.bdk.core.config.loader import BdkConfigLoader +from symphony.bdk.core.symphony_bdk import SymphonyBdk +from symphony.bdk.gen.pod_model.v3_room_attributes import V3RoomAttributes +from tests.bdk.integration.helpers import (BOT_USER_ID, FEED_BOT_USERNAME, + MSG_BOT_USERNAME, STREAM_ID, + SYMPHONY_HOST, TEST_RSA_KEY, + TEST_USER_ID, MessageListener, + datafeed_bot_config, + get_test_messages, + messenger_bot_config, send_messages) + +pytestmark = pytest.mark.asyncio + + +pytestmark = pytest.mark.skipif( + not all( + [ + STREAM_ID, + MSG_BOT_USERNAME, + FEED_BOT_USERNAME, + SYMPHONY_HOST, + TEST_RSA_KEY, + TEST_USER_ID, + BOT_USER_ID, + ] + ), + reason="Required environment variables for integration tests are not set " + "(STREAM_ID, MSG_BOT_USERNAME, FEED_BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY, TEST_USER_ID, BOT_USER_ID)", +) + +NUMBER_OF_MESSAGES = 3 + + +@pytest_asyncio.fixture +async def bdk(messenger_bot_config): + config = BdkConfigLoader.load_from_file(str(messenger_bot_config)) + async with SymphonyBdk(config) as bdk: + yield bdk + + +@pytest.mark.asyncio +async def test_bot_read_write_messages(bdk): + uuid = str(uuid4()) + # Given: test execution start time + since = int((datetime.now() - timedelta(seconds=2)).timestamp()) * 1000 + # Given: BDK is initialized with config + # When: messages are sent via bot + await send_messages(bdk.messages(), STREAM_ID, since, uuid) + # Then: messages are readable with the same bot + messages = await get_test_messages(bdk, since, uuid) + # Then: Expected messages are posted to the room + assert sorted(messages) == [ + f"{uuid}-{i}-{since}" for i in range(NUMBER_OF_MESSAGES) + ] + + +@pytest.mark.asyncio +async def test_bot_creates_stream_add_delete_user(bdk): + test_user = int(TEST_USER_ID) + # Given: Stream bdk creates a room + streams = bdk.streams() + room_result = await streams.create_room( + V3RoomAttributes(name="New fancy room", description="test room") + ) + room_id = room_result.room_system_info.id + # When: user is added to the room + await streams.add_member_to_room(test_user, room_id) + members = await streams.list_room_members(room_id) + # Then: user is present in the room + assert test_user in [m.id for m in members.value] + # When: user is removed from the room + await streams.remove_member_from_room(test_user, room_id) + # Then: user is deleted from the room + members_after_removal = await streams.list_room_members(room_id) + assert test_user not in [m.id for m in members_after_removal.value] + + +@pytest.mark.asyncio +async def test_datafeed_receives_message(bdk: SymphonyBdk, datafeed_bot_config): + """ + Test is running 2 bdk instances at the same time. + Data feed filters its own events so in order to see that feed is working + Two parale bots are added + + """ + # Given: message listener is initialized with expected message id + unique_id = str(uuid4()) + message_content = f"Message for datafeed test. ID: {unique_id}" + listener = MessageListener(message_to_find=message_content) + # Given: members are added to the room + bdk.datafeed().subscribe(listener) + await bdk.streams().add_member_to_room(int(BOT_USER_ID), STREAM_ID) + datafeed_task = asyncio.create_task(bdk.datafeed().start()) + await asyncio.sleep(3) + config = BdkConfigLoader.load_from_file(str(datafeed_bot_config)) + async with SymphonyBdk(config) as another_bot: + # When: another bot instance sends a message to the needed room + await another_bot.messages().send_message(STREAM_ID, message_content) + try: + # Then: particular message is received by datafeed instance + await asyncio.wait_for(listener.message_received_event.wait(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Datafeed did not receive the message within the timeout period.") + finally: + await bdk.datafeed().stop() + await datafeed_task + bdk.datafeed().unsubscribe(listener) + + assert listener.message_received_event.is_set() diff --git a/tests/bdk/integration/helpers.py b/tests/bdk/integration/helpers.py new file mode 100644 index 00000000..5fb4a7a1 --- /dev/null +++ b/tests/bdk/integration/helpers.py @@ -0,0 +1,91 @@ +import asyncio +import os +import re + +import pytest +import pytest_asyncio +import yaml + +from symphony.bdk.core.config.loader import BdkConfigLoader +from symphony.bdk.core.service.datafeed.real_time_event_listener import \ + RealTimeEventListener +from symphony.bdk.core.symphony_bdk import SymphonyBdk +from symphony.bdk.gen.agent_model.v4_initiator import V4Initiator +from symphony.bdk.gen.agent_model.v4_message_sent import V4MessageSent + +STREAM_ID = os.getenv("STREAM_ID") +MSG_BOT_USERNAME = os.getenv("MSG_BOT_USERNAME") +FEED_BOT_USERNAME = os.getenv("FEED_BOT_USERNAME") +SYMPHONY_HOST = os.getenv("SYMPHONY_HOST") +TEST_RSA_KEY = os.getenv("TEST_RSA_KEY") +TEST_USER_ID = os.getenv("TEST_USER_ID") +BOT_USER_ID = os.getenv("BOT_USER_ID") +NUMBER_OF_MESSAGES = 3 + + +def generate_config(tmp_dir, bot_username): + key_path = tmp_dir / "key.pem" + config_path = tmp_dir / "config.yaml" + + bot_config_dict = { + "host": SYMPHONY_HOST, + "bot": {"username": bot_username, "privateKey": {"path": str(key_path)}}, + } + + key_path.write_text(TEST_RSA_KEY) + with config_path.open("w") as config_file: + yaml.dump(bot_config_dict, config_file) + + return config_path + + +@pytest.fixture +def messenger_bot_config(tmp_path_factory): + tmp_dir = tmp_path_factory.mktemp("bdk_config_messenger") + return generate_config(tmp_dir, MSG_BOT_USERNAME) + + +@pytest.fixture +def datafeed_bot_config(tmp_path_factory): + tmp_dir = tmp_path_factory.mktemp("bdk_config_feed") + return generate_config(tmp_dir, FEED_BOT_USERNAME) + + +@pytest_asyncio.fixture +async def bdk(messenger_bot_config): + config = BdkConfigLoader.load_from_file(str(messenger_bot_config)) + async with SymphonyBdk(config) as bdk: + yield bdk + + +async def send_messages(messages, stream_id, since, uuid): + for i in range(NUMBER_OF_MESSAGES): + await messages.send_message( + stream_id, f"{uuid}-{i}-{since}" + ) + + +async def get_test_messages(bdk, since, uuid): + messages = await bdk.messages().list_messages(STREAM_ID, since=since) + cleaned_messages_text = [ + re.sub(r"<[^>]+>", " ", msg["message"]).strip() for msg in messages + ] + return list( + filter( + lambda msg: msg.startswith(uuid), + cleaned_messages_text, + ) + ) + + +class MessageListener(RealTimeEventListener): + """A simple listener to capture a specific message from the datafeed.""" + + def __init__(self, message_to_find: str): + self._message_to_find = message_to_find + self.message_received_event = asyncio.Event() + + async def on_message_sent(self, initiator: V4Initiator, event: V4MessageSent): + message_text = re.sub(r"<[^>]+>", "", event.message.message).strip() + if self._message_to_find in message_text: + self.message_received_event.set() diff --git a/tests/bdk/integration/test_post_message.py b/tests/bdk/integration/test_post_message.py deleted file mode 100644 index d26a21ae..00000000 --- a/tests/bdk/integration/test_post_message.py +++ /dev/null @@ -1,84 +0,0 @@ -import os -import re -from datetime import datetime, timedelta -from pathlib import Path -from uuid import uuid4 - -import pytest -import yaml - -from symphony.bdk.core.config.loader import BdkConfigLoader -from symphony.bdk.core.symphony_bdk import SymphonyBdk - -STREAM_ID = os.getenv("STREAM_ID") -BOT_USERNAME = os.getenv("BOT_USERNAME") -SYMPHONY_HOST = os.getenv("SYMPHONY_HOST") -TEST_RSA_KEY = os.getenv("TEST_RSA_KEY") - -# Skip all tests in this file if the required environment variables are not set. -pytestmark = pytest.mark.skipif( - not all([STREAM_ID, BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY]), - reason="Required environment variables for integration tests are not set " - "(STREAM_ID, BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY)", -) - -NUMBER_OF_MESSAGES = 3 - - -@pytest.fixture -def bot_config_path(tmp_path: Path): - key_path = tmp_path / "key.pem" - config_path = tmp_path / "config.yaml" - - bot_config_dict = { - "host": SYMPHONY_HOST, - "bot": {"username": BOT_USERNAME, "privateKey": {"path": str(key_path)}}, - } - - # Write the key and config files - key_path.write_text(TEST_RSA_KEY) - with config_path.open("w") as config_file: - yaml.dump(bot_config_dict, config_file) - - yield config_path - - os.remove(key_path), os.remove(config_path) - - -async def send_messages(messages, stream_id, since, uuid): - for i in range(NUMBER_OF_MESSAGES): - await messages.send_message( - stream_id, f"{uuid}-{i}-{since}" - ) - - -async def get_test_messages(bdk, since, uuid): - messages = await bdk.messages().list_messages(STREAM_ID, since=since) - cleaned_messages_text = [ - re.sub(r"<[^>]+>", " ", msg["message"]).strip() for msg in messages - ] - return list( - filter( - lambda msg: msg.startswith(uuid), - cleaned_messages_text, - ) - ) - - - -@pytest.mark.asyncio -async def test_bot_read_write_messages(bot_config_path): - uuid = str(uuid4()) - # Given: test execution start time - since = int((datetime.now() - timedelta(seconds=2)).timestamp()) * 1000 - # Given: BDK is initialized with config - config = BdkConfigLoader.load_from_symphony_dir(str(bot_config_path)) - async with SymphonyBdk(config) as bdk: - # When: messages are sent via bot - await send_messages(bdk.messages(), STREAM_ID, since, uuid) - # Then: messages are readable with the same bot - messages = await get_test_messages(bdk, since, uuid) - # Then: Expected messages are posted to the room - assert sorted(messages) == [ - f"{uuid}-{i}-{since}" for i in range(NUMBER_OF_MESSAGES) - ] From d658dd7f7383b3d0ad3327172c08826e0a4cb9ce Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Thu, 7 Aug 2025 23:04:00 +0300 Subject: [PATCH 09/18] CAIP-80 extend timeout --- tests/bdk/integration/e2e_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bdk/integration/e2e_test.py b/tests/bdk/integration/e2e_test.py index cd8dc436..0d83540c 100644 --- a/tests/bdk/integration/e2e_test.py +++ b/tests/bdk/integration/e2e_test.py @@ -105,7 +105,7 @@ async def test_datafeed_receives_message(bdk: SymphonyBdk, datafeed_bot_config): await another_bot.messages().send_message(STREAM_ID, message_content) try: # Then: particular message is received by datafeed instance - await asyncio.wait_for(listener.message_received_event.wait(), timeout=30) + await asyncio.wait_for(listener.message_received_event.wait(), timeout=300) except asyncio.TimeoutError: pytest.fail("Datafeed did not receive the message within the timeout period.") finally: From c5682bae4ab6ca2f074bbee835074c6fc532e9ed Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Fri, 8 Aug 2025 12:47:21 +0300 Subject: [PATCH 10/18] CAIP-80 added separate workflow for e2e --- .github/workflows/build.yml | 8 ---- .github/workflows/e2e-build.yml | 67 +++++++++++++++++++++++++++++++ tests/bdk/integration/e2e_test.py | 9 ++--- 3 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/e2e-build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d3a3330..581015ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,14 +54,6 @@ jobs: run: poetry install - name: Run tests with coverage - env: - TEST_RSA_KEY: ${{ secrets.TEST_RSA_KEY }} - MSG_BOT_USERNAME: ${{ vars.MSG_BOT_USERNAME }} - FEED_BOT_USERNAME: ${{ vars.FEED_BOT_USERNAME }} - STREAM_ID: ${{ vars.STREAM_ID }} - SYMPHONY_HOST: ${{ vars.SYMPHONY_HOST }} - TEST_USER_ID: ${{ vars.TEST_SYM_USER_ID }} - BOT_USER_ID: ${{ vars.BOT_USER_ID }} run: poetry run pytest timeout-minutes: 10 diff --git a/.github/workflows/e2e-build.yml b/.github/workflows/e2e-build.yml new file mode 100644 index 00000000..ed803411 --- /dev/null +++ b/.github/workflows/e2e-build.yml @@ -0,0 +1,67 @@ +name: build + +on: + push: + branches: [ "main", "*-rc" ] + pull_request: + branches: [ "main", "*-rc" ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ '3.12' ] + os: [ ubuntu-latest ] + include: + - os: ubuntu-latest + path: ~/.cache/pypoetry/virtualenvs + - os: macos-latest + path: ~/Library/Caches/pypoetry/virtualenvs + - os: windows-latest + path: ~\AppData\Local\pypoetry\Cache\virtualenvs + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + id: setup-python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Cache pip dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ steps.setup-python.outputs.python-version }} + + - name: Install Poetry + run: pip install poetry + + - name: Cache poetry + uses: actions/cache@v4 + with: + path: ${{ matrix.path }} + key: ${{ runner.os }}-poetry-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry-${{ steps.setup-python.outputs.python-version }}- + - name: Install dependencies + run: poetry install + + - name: Run e2e tests with coverage + env: + TEST_RSA_KEY: ${{ secrets.TEST_RSA_KEY }} + MSG_BOT_USERNAME: ${{ vars.MSG_BOT_USERNAME }} + FEED_BOT_USERNAME: ${{ vars.FEED_BOT_USERNAME }} + STREAM_ID: ${{ vars.STREAM_ID }} + SYMPHONY_HOST: ${{ vars.SYMPHONY_HOST }} + TEST_USER_ID: ${{ vars.TEST_SYM_USER_ID }} + BOT_USER_ID: ${{ vars.BOT_USER_ID }} + run: poetry run pytest + timeout-minutes: 10 + diff --git a/tests/bdk/integration/e2e_test.py b/tests/bdk/integration/e2e_test.py index 0d83540c..d34b250b 100644 --- a/tests/bdk/integration/e2e_test.py +++ b/tests/bdk/integration/e2e_test.py @@ -16,10 +16,9 @@ get_test_messages, messenger_bot_config, send_messages) -pytestmark = pytest.mark.asyncio - - -pytestmark = pytest.mark.skipif( +pytestmark =[ + pytest.mark.asyncio, + pytest.mark.skipif( not all( [ STREAM_ID, @@ -34,7 +33,7 @@ reason="Required environment variables for integration tests are not set " "(STREAM_ID, MSG_BOT_USERNAME, FEED_BOT_USERNAME, SYMPHONY_HOST, TEST_RSA_KEY, TEST_USER_ID, BOT_USER_ID)", ) - +] NUMBER_OF_MESSAGES = 3 From bc40974244f8d2c905b0c039f5e34c39a483a243 Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Fri, 8 Aug 2025 12:49:03 +0300 Subject: [PATCH 11/18] CAIP-80 lock updated --- poetry.lock | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/poetry.lock b/poetry.lock index b2a6f7e5..b5b82bb3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -119,7 +119,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] +speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi"] [[package]] name = "aiosignal" @@ -195,12 +195,12 @@ files = [ ] [package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "babel" @@ -555,7 +555,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "cryptography" @@ -608,10 +608,10 @@ files = [ cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] -pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -872,12 +872,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -1729,7 +1729,7 @@ files = [ ] [package.extras] -dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1) ; python_version == \"3.4\"", "coverage", "flake8", "nose2", "readme-renderer (<25.0) ; python_version == \"3.4\"", "tox", "wheel", "zest.releaser[recommended]"] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] @@ -1745,13 +1745,13 @@ files = [ ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -1823,7 +1823,7 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast ; python_version < \"3.8\""] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] [[package]] name = "sphinx-basic-ng" @@ -2017,7 +2017,7 @@ files = [ ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2213,11 +2213,11 @@ files = [ ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] From b980a2f1dd5c1611c3a62723e9da858a0ce5b631 Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Fri, 8 Aug 2025 13:07:17 +0300 Subject: [PATCH 12/18] lock back --- poetry.lock | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/poetry.lock b/poetry.lock index b5b82bb3..b2a6f7e5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -119,7 +119,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -195,12 +195,12 @@ files = [ ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] [[package]] name = "babel" @@ -555,7 +555,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -608,10 +608,10 @@ files = [ cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] +pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -872,12 +872,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -1729,7 +1729,7 @@ files = [ ] [package.extras] -dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1) ; python_version == \"3.4\"", "coverage", "flake8", "nose2", "readme-renderer (<25.0) ; python_version == \"3.4\"", "tox", "wheel", "zest.releaser[recommended]"] doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] @@ -1745,13 +1745,13 @@ files = [ ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -1823,7 +1823,7 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast ; python_version < \"3.8\""] [[package]] name = "sphinx-basic-ng" @@ -2017,7 +2017,7 @@ files = [ ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2213,11 +2213,11 @@ files = [ ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] From a5404978e00b749e29f44fc4c88010783df42eca Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Fri, 8 Aug 2025 14:29:13 +0300 Subject: [PATCH 13/18] CAIP-80 fix workflow file --- .github/workflows/e2e-build.yml | 34 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/.github/workflows/e2e-build.yml b/.github/workflows/e2e-build.yml index ed803411..1cf748fb 100644 --- a/.github/workflows/e2e-build.yml +++ b/.github/workflows/e2e-build.yml @@ -1,5 +1,4 @@ -name: build - +name: E2E Tests on: push: branches: [ "main", "*-rc" ] @@ -7,33 +6,22 @@ on: branches: [ "main", "*-rc" ] jobs: - build: + e2e-test: runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ '3.12' ] - os: [ ubuntu-latest ] - include: - - os: ubuntu-latest - path: ~/.cache/pypoetry/virtualenvs - - os: macos-latest - path: ~/Library/Caches/pypoetry/virtualenvs - - os: windows-latest - path: ~\AppData\Local\pypoetry\Cache\virtualenvs steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python id: setup-python uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: '3.12' - name: Get pip cache dir id: pip-cache run: | - echo "::set-output name=dir::$(pip cache dir)" + echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: Cache pip dependencies uses: actions/cache@v4 with: @@ -43,17 +31,20 @@ jobs: - name: Install Poetry run: pip install poetry - - name: Cache poetry + - name: Install Poetry plugins + run: poetry self add poetry-plugin-export + + - name: Cache poetry virtualenv uses: actions/cache@v4 with: - path: ${{ matrix.path }} + path: ~/.cache/pypoetry/virtualenvs key: ${{ runner.os }}-poetry-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} restore-keys: | ${{ runner.os }}-poetry-${{ steps.setup-python.outputs.python-version }}- - name: Install dependencies run: poetry install - - name: Run e2e tests with coverage + - name: Run E2E tests env: TEST_RSA_KEY: ${{ secrets.TEST_RSA_KEY }} MSG_BOT_USERNAME: ${{ vars.MSG_BOT_USERNAME }} @@ -62,6 +53,5 @@ jobs: SYMPHONY_HOST: ${{ vars.SYMPHONY_HOST }} TEST_USER_ID: ${{ vars.TEST_SYM_USER_ID }} BOT_USER_ID: ${{ vars.BOT_USER_ID }} - run: poetry run pytest + run: poetry run pytest -m e2e timeout-minutes: 10 - From 32265af74547328f69d799898d71480b27b69383 Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Fri, 8 Aug 2025 14:33:11 +0300 Subject: [PATCH 14/18] CAIP-80 add e2e marker --- pyproject.toml | 1 + tests/bdk/integration/e2e_test.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e2858a52..5fc52c5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] addopts = "--junitxml=test-results/junit.xml --cov --cov-report=html" testpaths = ["tests"] +markers = ["e2e: marks tests as end-to-end tests"] norecursedirs = ["*.egg", ".*", "build", "dist", "venv", "legacy"] junit_logging = "all" diff --git a/tests/bdk/integration/e2e_test.py b/tests/bdk/integration/e2e_test.py index d34b250b..83a3fc71 100644 --- a/tests/bdk/integration/e2e_test.py +++ b/tests/bdk/integration/e2e_test.py @@ -18,6 +18,7 @@ pytestmark =[ pytest.mark.asyncio, + pytest.mark.e2e, pytest.mark.skipif( not all( [ From 4f8147a33102a1ec348dece389daa852900f342a Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Fri, 8 Aug 2025 14:37:50 +0300 Subject: [PATCH 15/18] CAIP-80 disable coverage for e2e --- .github/workflows/e2e-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-build.yml b/.github/workflows/e2e-build.yml index 1cf748fb..4b4b7ec4 100644 --- a/.github/workflows/e2e-build.yml +++ b/.github/workflows/e2e-build.yml @@ -53,5 +53,5 @@ jobs: SYMPHONY_HOST: ${{ vars.SYMPHONY_HOST }} TEST_USER_ID: ${{ vars.TEST_SYM_USER_ID }} BOT_USER_ID: ${{ vars.BOT_USER_ID }} - run: poetry run pytest -m e2e + run: poetry run pytest -m e2e --no-cov timeout-minutes: 10 From 6e0a16b221bfda8551cea5ae52dcabbc7c280352 Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Fri, 8 Aug 2025 14:44:24 +0300 Subject: [PATCH 16/18] test workflow for branch --- .github/workflows/e2e-build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/e2e-build.yml b/.github/workflows/e2e-build.yml index 4b4b7ec4..9477f282 100644 --- a/.github/workflows/e2e-build.yml +++ b/.github/workflows/e2e-build.yml @@ -1,9 +1,7 @@ name: E2E Tests on: push: - branches: [ "main", "*-rc" ] - pull_request: - branches: [ "main", "*-rc" ] + branches: [ "CAIP-80-extra-tests-based-on-examples" ] jobs: e2e-test: From 550dd23c800e40494922ebf1b2b0ed1a8852e6c4 Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Fri, 8 Aug 2025 14:46:09 +0300 Subject: [PATCH 17/18] CAIP-80 disable e2e for each commit --- .github/workflows/e2e-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-build.yml b/.github/workflows/e2e-build.yml index 9477f282..c0046f8a 100644 --- a/.github/workflows/e2e-build.yml +++ b/.github/workflows/e2e-build.yml @@ -1,7 +1,7 @@ -name: E2E Tests +name: e2e test on: push: - branches: [ "CAIP-80-extra-tests-based-on-examples" ] + branches: [ "main" ] jobs: e2e-test: From 76d3f1b61adb293f45a5c6b35c74381210341168 Mon Sep 17 00:00:00 2001 From: Bohdan Heryk Date: Mon, 11 Aug 2025 12:33:19 +0300 Subject: [PATCH 18/18] CAIP-80 adding typing --- tests/bdk/integration/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/bdk/integration/helpers.py b/tests/bdk/integration/helpers.py index 5fb4a7a1..240a9b1e 100644 --- a/tests/bdk/integration/helpers.py +++ b/tests/bdk/integration/helpers.py @@ -5,6 +5,7 @@ import pytest import pytest_asyncio import yaml +from pathlib import Path from symphony.bdk.core.config.loader import BdkConfigLoader from symphony.bdk.core.service.datafeed.real_time_event_listener import \ @@ -23,7 +24,7 @@ NUMBER_OF_MESSAGES = 3 -def generate_config(tmp_dir, bot_username): +def generate_config(tmp_dir: Path, bot_username: str): key_path = tmp_dir / "key.pem" config_path = tmp_dir / "config.yaml"