From e11bfd1da7411c7252a7b6ca7ababbf4729b1a62 Mon Sep 17 00:00:00 2001 From: Giannis Date: Tue, 9 Dec 2025 22:42:33 +0200 Subject: [PATCH 1/7] test: add subscription_handle unit tests and CI workflow; docs: add test running instructions Signed-off-by: Giannis --- .github/workflows/ci.yml | 28 +++++++++++++++++++++++ CONTRIBUTING.md | 23 ++++++++++++++++++- tests/test_subscription_handle.py | 38 +++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/test_subscription_handle.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..56e1d22d5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test-subscription-handle: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install test deps + run: | + python -m pip install --upgrade pip + python -m pip install pytest + + - name: Run subscription_handle tests + run: | + python -m pytest -q tests/test_subscription_handle.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67bfbb67c..7215da24d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -166,4 +166,25 @@ Thank you for contributing to the Hiero Python SDK! 🎉 - **Need help or want to connect?** Join our community on Discord! See the **[Discord Joining Guide](docs/discord.md)** for detailed steps on how to join the LFDT server - **Quick Links:** - Join the main [Linux Foundation Decentralized Trust (LFDT) Discord Server](https://discord.gg/hyperledger). - - Go directly to the [#hiero-python-sdk channel](https://discord.com/channels/905194001349627914/1336494517544681563) \ No newline at end of file + - Go directly to the [#hiero-python-sdk channel](https://discord.com/channels/905194001349627914/1336494517544681563) + +--- + +## Running tests locally + +During development you may want to run a single test file in isolation to avoid importing the project's package-level `__init__` (which imports generated protobuf modules). To run a single test file without loading project-wide fixtures, use the project's virtualenv Python and run pytest on the path: + +PowerShell example: +```powershell +# activate virtualenv (if not already active) +C:/Users/Giannis/OneDrive/Programming/hiero-sdk-python/.venv/Scripts/Activate.ps1 + +# install test deps if needed +python -m pip install -U pip +python -m pip install pytest + +# run a single test file +python -m pytest -q tests/test_subscription_handle.py +``` + +If you want to run a subset of tests, use `-k` with a keyword expression or provide a directory/file path. This helps avoid heavy `conftest.py` imports during quick iteration. diff --git a/tests/test_subscription_handle.py b/tests/test_subscription_handle.py new file mode 100644 index 000000000..f67c73ebb --- /dev/null +++ b/tests/test_subscription_handle.py @@ -0,0 +1,38 @@ +from unittest.mock import Mock +import importlib.util +from pathlib import Path + +# Load the module directly from the source file to avoid importing the +# package `hiero_sdk_python` (its top-level __init__ triggers imports of +# many modules that require generated protobufs during test collection). +repo_root = Path(__file__).resolve().parent.parent +module_path = repo_root / "src" / "hiero_sdk_python" / "utils" / "subscription_handle.py" +spec = importlib.util.spec_from_file_location("subscription_handle", str(module_path)) +subscription_handle = importlib.util.module_from_spec(spec) +spec.loader.exec_module(subscription_handle) +SubscriptionHandle = subscription_handle.SubscriptionHandle + + +def test_not_cancelled_by_default(): + handle = SubscriptionHandle() + assert not handle.is_cancelled() + + +def test_cancel_marks_as_cancelled(): + handle = SubscriptionHandle() + handle.cancel() + assert handle.is_cancelled() + + +def test_set_thread_and_join_calls_thread_join_with_timeout(): + handle = SubscriptionHandle() + mock_thread = Mock() + handle.set_thread(mock_thread) + handle.join(timeout=0.25) + mock_thread.join.assert_called_once_with(0.25) + + +def test_join_without_thread_raises_nothing(): + handle = SubscriptionHandle() + # should not raise + handle.join() From 92ab14842952e7bf272c2bc62560016c1f211156 Mon Sep 17 00:00:00 2001 From: Giannis Date: Wed, 10 Dec 2025 13:38:41 +0200 Subject: [PATCH 2/7] All maintainer feedback addressed Signed-off-by: Giannis --- CHANGELOG.md | 1 + tests/unit/test_subscription_handle.py | 28 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/unit/test_subscription_handle.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e1de1862..3d1a6955a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1. ## [Unreleased] ### Added +- Added unit tests for `SubscriptionHandle` class covering cancellation state, thread management, and join operations. - Modularized `transfer_transaction_fungible` example by introducing `account_balance_query()` & `transfer_transaction()`.Renamed `transfer_tokens()` → `main()` - Phase 2 of the inactivity-unassign bot:Automatically detects stale open pull requests (no commit activity for 21+ days), comments with a helpful InactivityBot message, closes the stale PR, and unassigns the contributor from the linked issue. - Added **str**() to CustomFixedFee and updated examples and tests accordingly. diff --git a/tests/unit/test_subscription_handle.py b/tests/unit/test_subscription_handle.py new file mode 100644 index 000000000..230680d6e --- /dev/null +++ b/tests/unit/test_subscription_handle.py @@ -0,0 +1,28 @@ +from unittest.mock import Mock + +from hiero_sdk_python.utils.subscription_handle import SubscriptionHandle + + +def test_not_cancelled_by_default(): + handle = SubscriptionHandle() + assert not handle.is_cancelled() + + +def test_cancel_marks_as_cancelled(): + handle = SubscriptionHandle() + handle.cancel() + assert handle.is_cancelled() + + +def test_set_thread_and_join_calls_thread_join_with_timeout(): + handle = SubscriptionHandle() + mock_thread = Mock() + handle.set_thread(mock_thread) + handle.join(timeout=0.25) + mock_thread.join.assert_called_once_with(0.25) + + +def test_join_without_thread_raises_nothing(): + handle = SubscriptionHandle() + # should not raise + handle.join() From f2634db29d739c4118351a4432de86cf99e99750 Mon Sep 17 00:00:00 2001 From: Giannis Date: Wed, 10 Dec 2025 13:44:36 +0200 Subject: [PATCH 3/7] final fix Signed-off-by: Giannis --- tests/_shims/hiero_sdk_python/hapi/__init__.py | 5 +++++ .../_shims/hiero_sdk_python/hapi/mirror/__init__.py | 1 + .../hapi/mirror/consensus_service_pb2_grpc.py | 7 +++++++ .../hiero_sdk_python/hapi/services/__init__.py | 1 + .../hapi/services/basic_types_pb2.py | 13 +++++++++++++ .../hiero_sdk_python/hapi/services/timestamp_pb2.py | 6 ++++++ tools/_mypy_minimal.ini | 2 ++ 7 files changed, 35 insertions(+) create mode 100644 tests/_shims/hiero_sdk_python/hapi/__init__.py create mode 100644 tests/_shims/hiero_sdk_python/hapi/mirror/__init__.py create mode 100644 tests/_shims/hiero_sdk_python/hapi/mirror/consensus_service_pb2_grpc.py create mode 100644 tests/_shims/hiero_sdk_python/hapi/services/__init__.py create mode 100644 tests/_shims/hiero_sdk_python/hapi/services/basic_types_pb2.py create mode 100644 tests/_shims/hiero_sdk_python/hapi/services/timestamp_pb2.py create mode 100644 tools/_mypy_minimal.ini diff --git a/tests/_shims/hiero_sdk_python/hapi/__init__.py b/tests/_shims/hiero_sdk_python/hapi/__init__.py new file mode 100644 index 000000000..40d55cb3e --- /dev/null +++ b/tests/_shims/hiero_sdk_python/hapi/__init__.py @@ -0,0 +1,5 @@ +"""Test shims for generated `hapi` modules. + +These are minimal stand-ins used during unit testing when the real +generated protobuf/grpc modules are not present. +""" diff --git a/tests/_shims/hiero_sdk_python/hapi/mirror/__init__.py b/tests/_shims/hiero_sdk_python/hapi/mirror/__init__.py new file mode 100644 index 000000000..dd1edb72c --- /dev/null +++ b/tests/_shims/hiero_sdk_python/hapi/mirror/__init__.py @@ -0,0 +1 @@ +"""Shim for `hiero_sdk_python.hapi.mirror` used in tests.""" diff --git a/tests/_shims/hiero_sdk_python/hapi/mirror/consensus_service_pb2_grpc.py b/tests/_shims/hiero_sdk_python/hapi/mirror/consensus_service_pb2_grpc.py new file mode 100644 index 000000000..9a6deda5b --- /dev/null +++ b/tests/_shims/hiero_sdk_python/hapi/mirror/consensus_service_pb2_grpc.py @@ -0,0 +1,7 @@ +"""Minimal stub of generated gRPC module used by `client.Client` for tests.""" + +class ConsensusServiceStub: + """A tiny stub of the real gRPC stub. Methods are intentionally omitted.""" + + def __init__(self, channel): + self._channel = channel diff --git a/tests/_shims/hiero_sdk_python/hapi/services/__init__.py b/tests/_shims/hiero_sdk_python/hapi/services/__init__.py new file mode 100644 index 000000000..34d4c074c --- /dev/null +++ b/tests/_shims/hiero_sdk_python/hapi/services/__init__.py @@ -0,0 +1 @@ +"""Shim for `hiero_sdk_python.hapi.services` providing minimal protobuf stubs for tests.""" diff --git a/tests/_shims/hiero_sdk_python/hapi/services/basic_types_pb2.py b/tests/_shims/hiero_sdk_python/hapi/services/basic_types_pb2.py new file mode 100644 index 000000000..fc18acaaa --- /dev/null +++ b/tests/_shims/hiero_sdk_python/hapi/services/basic_types_pb2.py @@ -0,0 +1,13 @@ +"""Minimal shim for basic_types_pb2 used in tests.""" + +class _ProtoMessage: + def CopyFrom(self, other): + for k, v in getattr(other, "__dict__", {}).items(): + setattr(self, k, v) + + +class TransactionID(_ProtoMessage): + def __init__(self): + self.accountID = _ProtoMessage() + self.transactionValidStart = _ProtoMessage() + self.scheduled = False diff --git a/tests/_shims/hiero_sdk_python/hapi/services/timestamp_pb2.py b/tests/_shims/hiero_sdk_python/hapi/services/timestamp_pb2.py new file mode 100644 index 000000000..952155aec --- /dev/null +++ b/tests/_shims/hiero_sdk_python/hapi/services/timestamp_pb2.py @@ -0,0 +1,6 @@ +"""Minimal shim for timestamp_pb2.Timestamp used in tests.""" + +class Timestamp: + def __init__(self, seconds: int = 0, nanos: int = 0): + self.seconds = int(seconds) + self.nanos = int(nanos) diff --git a/tools/_mypy_minimal.ini b/tools/_mypy_minimal.ini new file mode 100644 index 000000000..976ba0294 --- /dev/null +++ b/tools/_mypy_minimal.ini @@ -0,0 +1,2 @@ +[mypy] +ignore_missing_imports = True From 764bf1fa0be1d8c5ef6a64c8d3a3a75ba531c79f Mon Sep 17 00:00:00 2001 From: Giannis Date: Tue, 9 Dec 2025 22:42:33 +0200 Subject: [PATCH 4/7] That is the correct commit. i made a mistake before Signed-off-by: Giannis --- .github/workflows/ci.yml | 28 ----------------------- tests/test_subscription_handle.py | 38 ------------------------------- 2 files changed, 66 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 tests/test_subscription_handle.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 56e1d22d5..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test-subscription-handle: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install test deps - run: | - python -m pip install --upgrade pip - python -m pip install pytest - - - name: Run subscription_handle tests - run: | - python -m pytest -q tests/test_subscription_handle.py diff --git a/tests/test_subscription_handle.py b/tests/test_subscription_handle.py deleted file mode 100644 index f67c73ebb..000000000 --- a/tests/test_subscription_handle.py +++ /dev/null @@ -1,38 +0,0 @@ -from unittest.mock import Mock -import importlib.util -from pathlib import Path - -# Load the module directly from the source file to avoid importing the -# package `hiero_sdk_python` (its top-level __init__ triggers imports of -# many modules that require generated protobufs during test collection). -repo_root = Path(__file__).resolve().parent.parent -module_path = repo_root / "src" / "hiero_sdk_python" / "utils" / "subscription_handle.py" -spec = importlib.util.spec_from_file_location("subscription_handle", str(module_path)) -subscription_handle = importlib.util.module_from_spec(spec) -spec.loader.exec_module(subscription_handle) -SubscriptionHandle = subscription_handle.SubscriptionHandle - - -def test_not_cancelled_by_default(): - handle = SubscriptionHandle() - assert not handle.is_cancelled() - - -def test_cancel_marks_as_cancelled(): - handle = SubscriptionHandle() - handle.cancel() - assert handle.is_cancelled() - - -def test_set_thread_and_join_calls_thread_join_with_timeout(): - handle = SubscriptionHandle() - mock_thread = Mock() - handle.set_thread(mock_thread) - handle.join(timeout=0.25) - mock_thread.join.assert_called_once_with(0.25) - - -def test_join_without_thread_raises_nothing(): - handle = SubscriptionHandle() - # should not raise - handle.join() From 5fb50a8bb8c469c14574afcb8e3e234a640ab46b Mon Sep 17 00:00:00 2001 From: Giannis Date: Wed, 10 Dec 2025 15:05:33 +0200 Subject: [PATCH 5/7] docs: remove out-of-scope test documentation from CONTRIBUTING.md Signed-off-by: Giannis --- CONTRIBUTING.md | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7215da24d..b2974d7a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -168,23 +168,3 @@ Thank you for contributing to the Hiero Python SDK! 🎉 - Join the main [Linux Foundation Decentralized Trust (LFDT) Discord Server](https://discord.gg/hyperledger). - Go directly to the [#hiero-python-sdk channel](https://discord.com/channels/905194001349627914/1336494517544681563) ---- - -## Running tests locally - -During development you may want to run a single test file in isolation to avoid importing the project's package-level `__init__` (which imports generated protobuf modules). To run a single test file without loading project-wide fixtures, use the project's virtualenv Python and run pytest on the path: - -PowerShell example: -```powershell -# activate virtualenv (if not already active) -C:/Users/Giannis/OneDrive/Programming/hiero-sdk-python/.venv/Scripts/Activate.ps1 - -# install test deps if needed -python -m pip install -U pip -python -m pip install pytest - -# run a single test file -python -m pytest -q tests/test_subscription_handle.py -``` - -If you want to run a subset of tests, use `-k` with a keyword expression or provide a directory/file path. This helps avoid heavy `conftest.py` imports during quick iteration. From 0a7a064588b9c9d2a55ac970c01181d934952e17 Mon Sep 17 00:00:00 2001 From: Giannis Date: Wed, 10 Dec 2025 15:17:30 +0200 Subject: [PATCH 6/7] test: remove unnecessary shims directory Signed-off-by: Giannis --- tests/_shims/hiero_sdk_python/hapi/__init__.py | 5 ----- .../_shims/hiero_sdk_python/hapi/mirror/__init__.py | 1 - .../hapi/mirror/consensus_service_pb2_grpc.py | 7 ------- .../hiero_sdk_python/hapi/services/__init__.py | 1 - .../hapi/services/basic_types_pb2.py | 13 ------------- .../hiero_sdk_python/hapi/services/timestamp_pb2.py | 6 ------ 6 files changed, 33 deletions(-) delete mode 100644 tests/_shims/hiero_sdk_python/hapi/__init__.py delete mode 100644 tests/_shims/hiero_sdk_python/hapi/mirror/__init__.py delete mode 100644 tests/_shims/hiero_sdk_python/hapi/mirror/consensus_service_pb2_grpc.py delete mode 100644 tests/_shims/hiero_sdk_python/hapi/services/__init__.py delete mode 100644 tests/_shims/hiero_sdk_python/hapi/services/basic_types_pb2.py delete mode 100644 tests/_shims/hiero_sdk_python/hapi/services/timestamp_pb2.py diff --git a/tests/_shims/hiero_sdk_python/hapi/__init__.py b/tests/_shims/hiero_sdk_python/hapi/__init__.py deleted file mode 100644 index 40d55cb3e..000000000 --- a/tests/_shims/hiero_sdk_python/hapi/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Test shims for generated `hapi` modules. - -These are minimal stand-ins used during unit testing when the real -generated protobuf/grpc modules are not present. -""" diff --git a/tests/_shims/hiero_sdk_python/hapi/mirror/__init__.py b/tests/_shims/hiero_sdk_python/hapi/mirror/__init__.py deleted file mode 100644 index dd1edb72c..000000000 --- a/tests/_shims/hiero_sdk_python/hapi/mirror/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Shim for `hiero_sdk_python.hapi.mirror` used in tests.""" diff --git a/tests/_shims/hiero_sdk_python/hapi/mirror/consensus_service_pb2_grpc.py b/tests/_shims/hiero_sdk_python/hapi/mirror/consensus_service_pb2_grpc.py deleted file mode 100644 index 9a6deda5b..000000000 --- a/tests/_shims/hiero_sdk_python/hapi/mirror/consensus_service_pb2_grpc.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Minimal stub of generated gRPC module used by `client.Client` for tests.""" - -class ConsensusServiceStub: - """A tiny stub of the real gRPC stub. Methods are intentionally omitted.""" - - def __init__(self, channel): - self._channel = channel diff --git a/tests/_shims/hiero_sdk_python/hapi/services/__init__.py b/tests/_shims/hiero_sdk_python/hapi/services/__init__.py deleted file mode 100644 index 34d4c074c..000000000 --- a/tests/_shims/hiero_sdk_python/hapi/services/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Shim for `hiero_sdk_python.hapi.services` providing minimal protobuf stubs for tests.""" diff --git a/tests/_shims/hiero_sdk_python/hapi/services/basic_types_pb2.py b/tests/_shims/hiero_sdk_python/hapi/services/basic_types_pb2.py deleted file mode 100644 index fc18acaaa..000000000 --- a/tests/_shims/hiero_sdk_python/hapi/services/basic_types_pb2.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Minimal shim for basic_types_pb2 used in tests.""" - -class _ProtoMessage: - def CopyFrom(self, other): - for k, v in getattr(other, "__dict__", {}).items(): - setattr(self, k, v) - - -class TransactionID(_ProtoMessage): - def __init__(self): - self.accountID = _ProtoMessage() - self.transactionValidStart = _ProtoMessage() - self.scheduled = False diff --git a/tests/_shims/hiero_sdk_python/hapi/services/timestamp_pb2.py b/tests/_shims/hiero_sdk_python/hapi/services/timestamp_pb2.py deleted file mode 100644 index 952155aec..000000000 --- a/tests/_shims/hiero_sdk_python/hapi/services/timestamp_pb2.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Minimal shim for timestamp_pb2.Timestamp used in tests.""" - -class Timestamp: - def __init__(self, seconds: int = 0, nanos: int = 0): - self.seconds = int(seconds) - self.nanos = int(nanos) From a851971e76281cfc30508284338df7765e406ec9 Mon Sep 17 00:00:00 2001 From: Giannis Date: Wed, 10 Dec 2025 15:56:14 +0200 Subject: [PATCH 7/7] chore: remove unintended mypy_minimal.ini file Signed-off-by: Giannis --- tools/_mypy_minimal.ini | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 tools/_mypy_minimal.ini diff --git a/tools/_mypy_minimal.ini b/tools/_mypy_minimal.ini deleted file mode 100644 index 976ba0294..000000000 --- a/tools/_mypy_minimal.ini +++ /dev/null @@ -1,2 +0,0 @@ -[mypy] -ignore_missing_imports = True