iwa 0.0.0__py3-none-any.whl → 0.0.1a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- conftest.py +22 -0
- iwa/__init__.py +1 -0
- iwa/__main__.py +6 -0
- iwa/core/__init__.py +1 -0
- iwa/core/chain/__init__.py +68 -0
- iwa/core/chain/errors.py +47 -0
- iwa/core/chain/interface.py +514 -0
- iwa/core/chain/manager.py +38 -0
- iwa/core/chain/models.py +128 -0
- iwa/core/chain/rate_limiter.py +193 -0
- iwa/core/cli.py +210 -0
- iwa/core/constants.py +28 -0
- iwa/core/contracts/__init__.py +1 -0
- iwa/core/contracts/contract.py +297 -0
- iwa/core/contracts/erc20.py +79 -0
- iwa/core/contracts/multisend.py +71 -0
- iwa/core/db.py +317 -0
- iwa/core/keys.py +361 -0
- iwa/core/mnemonic.py +385 -0
- iwa/core/models.py +344 -0
- iwa/core/monitor.py +209 -0
- iwa/core/plugins.py +45 -0
- iwa/core/pricing.py +91 -0
- iwa/core/services/__init__.py +17 -0
- iwa/core/services/account.py +57 -0
- iwa/core/services/balance.py +113 -0
- iwa/core/services/plugin.py +88 -0
- iwa/core/services/safe.py +392 -0
- iwa/core/services/transaction.py +172 -0
- iwa/core/services/transfer/__init__.py +166 -0
- iwa/core/services/transfer/base.py +260 -0
- iwa/core/services/transfer/erc20.py +247 -0
- iwa/core/services/transfer/multisend.py +386 -0
- iwa/core/services/transfer/native.py +262 -0
- iwa/core/services/transfer/swap.py +326 -0
- iwa/core/settings.py +95 -0
- iwa/core/tables.py +60 -0
- iwa/core/test.py +27 -0
- iwa/core/tests/test_wallet.py +255 -0
- iwa/core/types.py +59 -0
- iwa/core/ui.py +99 -0
- iwa/core/utils.py +59 -0
- iwa/core/wallet.py +380 -0
- iwa/plugins/__init__.py +1 -0
- iwa/plugins/gnosis/__init__.py +5 -0
- iwa/plugins/gnosis/cow/__init__.py +6 -0
- iwa/plugins/gnosis/cow/quotes.py +148 -0
- iwa/plugins/gnosis/cow/swap.py +403 -0
- iwa/plugins/gnosis/cow/types.py +20 -0
- iwa/plugins/gnosis/cow_utils.py +44 -0
- iwa/plugins/gnosis/plugin.py +68 -0
- iwa/plugins/gnosis/safe.py +157 -0
- iwa/plugins/gnosis/tests/test_cow.py +227 -0
- iwa/plugins/gnosis/tests/test_safe.py +100 -0
- iwa/plugins/olas/__init__.py +5 -0
- iwa/plugins/olas/constants.py +106 -0
- iwa/plugins/olas/contracts/activity_checker.py +93 -0
- iwa/plugins/olas/contracts/base.py +10 -0
- iwa/plugins/olas/contracts/mech.py +49 -0
- iwa/plugins/olas/contracts/mech_marketplace.py +43 -0
- iwa/plugins/olas/contracts/service.py +215 -0
- iwa/plugins/olas/contracts/staking.py +403 -0
- iwa/plugins/olas/importer.py +736 -0
- iwa/plugins/olas/mech_reference.py +135 -0
- iwa/plugins/olas/models.py +110 -0
- iwa/plugins/olas/plugin.py +243 -0
- iwa/plugins/olas/scripts/test_full_mech_flow.py +259 -0
- iwa/plugins/olas/scripts/test_simple_lifecycle.py +74 -0
- iwa/plugins/olas/service_manager/__init__.py +60 -0
- iwa/plugins/olas/service_manager/base.py +113 -0
- iwa/plugins/olas/service_manager/drain.py +336 -0
- iwa/plugins/olas/service_manager/lifecycle.py +839 -0
- iwa/plugins/olas/service_manager/mech.py +322 -0
- iwa/plugins/olas/service_manager/staking.py +530 -0
- iwa/plugins/olas/tests/conftest.py +30 -0
- iwa/plugins/olas/tests/test_importer.py +128 -0
- iwa/plugins/olas/tests/test_importer_error_handling.py +349 -0
- iwa/plugins/olas/tests/test_mech_contracts.py +85 -0
- iwa/plugins/olas/tests/test_olas_contracts.py +249 -0
- iwa/plugins/olas/tests/test_olas_integration.py +561 -0
- iwa/plugins/olas/tests/test_olas_models.py +144 -0
- iwa/plugins/olas/tests/test_olas_view.py +258 -0
- iwa/plugins/olas/tests/test_olas_view_actions.py +137 -0
- iwa/plugins/olas/tests/test_olas_view_modals.py +120 -0
- iwa/plugins/olas/tests/test_plugin.py +70 -0
- iwa/plugins/olas/tests/test_plugin_full.py +212 -0
- iwa/plugins/olas/tests/test_service_lifecycle.py +150 -0
- iwa/plugins/olas/tests/test_service_manager.py +1065 -0
- iwa/plugins/olas/tests/test_service_manager_errors.py +208 -0
- iwa/plugins/olas/tests/test_service_manager_flows.py +497 -0
- iwa/plugins/olas/tests/test_service_manager_mech.py +135 -0
- iwa/plugins/olas/tests/test_service_manager_rewards.py +360 -0
- iwa/plugins/olas/tests/test_service_manager_validation.py +145 -0
- iwa/plugins/olas/tests/test_service_staking.py +342 -0
- iwa/plugins/olas/tests/test_staking_integration.py +269 -0
- iwa/plugins/olas/tests/test_staking_validation.py +109 -0
- iwa/plugins/olas/tui/__init__.py +1 -0
- iwa/plugins/olas/tui/olas_view.py +952 -0
- iwa/tools/check_profile.py +67 -0
- iwa/tools/release.py +111 -0
- iwa/tools/reset_env.py +111 -0
- iwa/tools/reset_tenderly.py +362 -0
- iwa/tools/restore_backup.py +82 -0
- iwa/tui/__init__.py +1 -0
- iwa/tui/app.py +174 -0
- iwa/tui/modals/__init__.py +5 -0
- iwa/tui/modals/base.py +406 -0
- iwa/tui/rpc.py +63 -0
- iwa/tui/screens/__init__.py +1 -0
- iwa/tui/screens/wallets.py +749 -0
- iwa/tui/tests/test_app.py +125 -0
- iwa/tui/tests/test_rpc.py +139 -0
- iwa/tui/tests/test_wallets_refactor.py +30 -0
- iwa/tui/tests/test_widgets.py +123 -0
- iwa/tui/widgets/__init__.py +5 -0
- iwa/tui/widgets/base.py +100 -0
- iwa/tui/workers.py +42 -0
- iwa/web/dependencies.py +76 -0
- iwa/web/models.py +76 -0
- iwa/web/routers/accounts.py +115 -0
- iwa/web/routers/olas/__init__.py +24 -0
- iwa/web/routers/olas/admin.py +169 -0
- iwa/web/routers/olas/funding.py +135 -0
- iwa/web/routers/olas/general.py +29 -0
- iwa/web/routers/olas/services.py +378 -0
- iwa/web/routers/olas/staking.py +341 -0
- iwa/web/routers/state.py +65 -0
- iwa/web/routers/swap.py +617 -0
- iwa/web/routers/transactions.py +153 -0
- iwa/web/server.py +155 -0
- iwa/web/tests/test_web_endpoints.py +713 -0
- iwa/web/tests/test_web_olas.py +430 -0
- iwa/web/tests/test_web_swap.py +103 -0
- iwa-0.0.1a2.dist-info/METADATA +234 -0
- iwa-0.0.1a2.dist-info/RECORD +186 -0
- iwa-0.0.1a2.dist-info/entry_points.txt +2 -0
- iwa-0.0.1a2.dist-info/licenses/LICENSE +21 -0
- iwa-0.0.1a2.dist-info/top_level.txt +4 -0
- tests/legacy_cow.py +248 -0
- tests/legacy_safe.py +93 -0
- tests/legacy_transaction_retry_logic.py +51 -0
- tests/legacy_tui.py +440 -0
- tests/legacy_wallets_screen.py +554 -0
- tests/legacy_web.py +243 -0
- tests/test_account_service.py +120 -0
- tests/test_balance_service.py +186 -0
- tests/test_chain.py +490 -0
- tests/test_chain_interface.py +210 -0
- tests/test_cli.py +139 -0
- tests/test_contract.py +195 -0
- tests/test_db.py +180 -0
- tests/test_drain_coverage.py +174 -0
- tests/test_erc20.py +95 -0
- tests/test_gnosis_plugin.py +111 -0
- tests/test_keys.py +449 -0
- tests/test_legacy_wallet.py +1285 -0
- tests/test_main.py +13 -0
- tests/test_mnemonic.py +217 -0
- tests/test_modals.py +109 -0
- tests/test_models.py +213 -0
- tests/test_monitor.py +202 -0
- tests/test_multisend.py +84 -0
- tests/test_plugin_service.py +119 -0
- tests/test_pricing.py +143 -0
- tests/test_rate_limiter.py +199 -0
- tests/test_reset_tenderly.py +202 -0
- tests/test_rpc_view.py +73 -0
- tests/test_safe_coverage.py +139 -0
- tests/test_safe_service.py +168 -0
- tests/test_service_manager_integration.py +61 -0
- tests/test_service_manager_structure.py +31 -0
- tests/test_service_transaction.py +176 -0
- tests/test_staking_router.py +71 -0
- tests/test_staking_simple.py +31 -0
- tests/test_tables.py +76 -0
- tests/test_transaction_service.py +161 -0
- tests/test_transfer_multisend.py +179 -0
- tests/test_transfer_native.py +220 -0
- tests/test_transfer_security.py +93 -0
- tests/test_transfer_structure.py +37 -0
- tests/test_transfer_swap_unit.py +155 -0
- tests/test_ui_coverage.py +66 -0
- tests/test_utils.py +53 -0
- tests/test_workers.py +91 -0
- tools/verify_drain.py +183 -0
- __init__.py +0 -2
- hello.py +0 -6
- iwa-0.0.0.dist-info/METADATA +0 -10
- iwa-0.0.0.dist-info/RECORD +0 -6
- iwa-0.0.0.dist-info/top_level.txt +0 -2
- {iwa-0.0.0.dist-info → iwa-0.0.1a2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Tests for Olas Plugin."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
from iwa.plugins.olas.models import OlasConfig
|
|
6
|
+
from iwa.plugins.olas.plugin import OlasPlugin
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestOlasPlugin:
|
|
10
|
+
"""Tests for OlasPlugin class."""
|
|
11
|
+
|
|
12
|
+
def test_name_property(self):
|
|
13
|
+
"""Test plugin name property."""
|
|
14
|
+
plugin = OlasPlugin()
|
|
15
|
+
assert plugin.name == "olas"
|
|
16
|
+
|
|
17
|
+
def test_config_model_returns_olas_config(self):
|
|
18
|
+
"""Test config_model property returns OlasConfig."""
|
|
19
|
+
plugin = OlasPlugin()
|
|
20
|
+
assert plugin.config_model == OlasConfig
|
|
21
|
+
|
|
22
|
+
def test_get_cli_commands(self):
|
|
23
|
+
"""Test get_cli_commands returns dict with commands."""
|
|
24
|
+
plugin = OlasPlugin()
|
|
25
|
+
commands = plugin.get_cli_commands()
|
|
26
|
+
|
|
27
|
+
assert isinstance(commands, dict)
|
|
28
|
+
assert "create" in commands
|
|
29
|
+
assert callable(commands["create"])
|
|
30
|
+
|
|
31
|
+
@patch("iwa.plugins.olas.plugin.Wallet")
|
|
32
|
+
@patch("iwa.plugins.olas.plugin.ServiceManager")
|
|
33
|
+
def test_create_service(self, mock_sm_class, mock_wallet_class):
|
|
34
|
+
"""Test create_service calls ServiceManager.create."""
|
|
35
|
+
mock_wallet = MagicMock()
|
|
36
|
+
mock_wallet_class.return_value = mock_wallet
|
|
37
|
+
|
|
38
|
+
mock_manager = MagicMock()
|
|
39
|
+
mock_sm_class.return_value = mock_manager
|
|
40
|
+
|
|
41
|
+
plugin = OlasPlugin()
|
|
42
|
+
plugin.create_service(
|
|
43
|
+
chain_name="gnosis",
|
|
44
|
+
owner="0x1234",
|
|
45
|
+
token="OLAS",
|
|
46
|
+
bond=100,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
mock_wallet_class.assert_called_once()
|
|
50
|
+
mock_sm_class.assert_called_once_with(mock_wallet)
|
|
51
|
+
mock_manager.create.assert_called_once_with("gnosis", "0x1234", "OLAS", 100)
|
|
52
|
+
|
|
53
|
+
@patch("iwa.plugins.olas.plugin.Wallet")
|
|
54
|
+
@patch("iwa.plugins.olas.plugin.ServiceManager")
|
|
55
|
+
def test_create_service_defaults(self, mock_sm_class, mock_wallet_class):
|
|
56
|
+
"""Test create_service with default parameters."""
|
|
57
|
+
mock_wallet = MagicMock()
|
|
58
|
+
mock_wallet_class.return_value = mock_wallet
|
|
59
|
+
|
|
60
|
+
mock_manager = MagicMock()
|
|
61
|
+
mock_sm_class.return_value = mock_manager
|
|
62
|
+
|
|
63
|
+
plugin = OlasPlugin()
|
|
64
|
+
# Note: when called directly (not via typer), defaults are OptionInfo objects
|
|
65
|
+
# so we can only verify the method was called
|
|
66
|
+
plugin.create_service()
|
|
67
|
+
|
|
68
|
+
mock_wallet_class.assert_called_once()
|
|
69
|
+
mock_sm_class.assert_called_once_with(mock_wallet)
|
|
70
|
+
mock_manager.create.assert_called_once() # Any arguments
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Integration tests for OlasPlugin."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import typer
|
|
7
|
+
from typer.testing import CliRunner
|
|
8
|
+
|
|
9
|
+
from iwa.plugins.olas.importer import DiscoveredKey, DiscoveredService, ImportResult
|
|
10
|
+
from iwa.plugins.olas.plugin import OlasPlugin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def plugin():
|
|
15
|
+
"""Mock Olas plugin."""
|
|
16
|
+
return OlasPlugin()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def runner():
|
|
21
|
+
"""CLI runner fixture."""
|
|
22
|
+
return CliRunner()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_plugin_metadata(plugin):
|
|
26
|
+
"""Test plugin metadata methods."""
|
|
27
|
+
assert plugin.name == "olas"
|
|
28
|
+
assert plugin.config_model.__name__ == "OlasConfig"
|
|
29
|
+
assert "create" in plugin.get_cli_commands()
|
|
30
|
+
assert "import" in plugin.get_cli_commands()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_get_tui_view(plugin):
|
|
34
|
+
"""Test TUI view creation."""
|
|
35
|
+
with patch("iwa.plugins.olas.tui.olas_view.OlasView") as mock_view:
|
|
36
|
+
view = plugin.get_tui_view()
|
|
37
|
+
assert view is not None
|
|
38
|
+
mock_view.assert_called_once()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_create_service_cli(plugin, runner):
|
|
42
|
+
"""Test create_service CLI command."""
|
|
43
|
+
app = typer.Typer()
|
|
44
|
+
app.command()(plugin.create_service)
|
|
45
|
+
|
|
46
|
+
with (
|
|
47
|
+
patch("iwa.plugins.olas.plugin.ServiceManager") as mock_sm_cls,
|
|
48
|
+
patch("iwa.plugins.olas.plugin.Wallet"),
|
|
49
|
+
):
|
|
50
|
+
mock_sm = mock_sm_cls.return_value
|
|
51
|
+
result = runner.invoke(app, ["--chain", "gnosis", "--bond", "1"])
|
|
52
|
+
assert result.exit_code == 0
|
|
53
|
+
mock_sm.create.assert_called_once()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_import_services_cli_scan_only(plugin, runner):
|
|
57
|
+
"""Test import_services CLI in dry-run mode."""
|
|
58
|
+
app = typer.Typer()
|
|
59
|
+
app.command()(plugin.import_services)
|
|
60
|
+
|
|
61
|
+
with (
|
|
62
|
+
patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls,
|
|
63
|
+
patch.object(OlasPlugin, "_get_safe_signers", return_value=(None, None)),
|
|
64
|
+
):
|
|
65
|
+
mock_importer = mock_importer_cls.return_value
|
|
66
|
+
mock_importer.scan_directory.return_value = [
|
|
67
|
+
DiscoveredService(service_id=1, service_name="Test", chain_name="gnosis")
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
# Test dry-run
|
|
71
|
+
result = runner.invoke(app, ["/tmp/test", "--dry-run"])
|
|
72
|
+
assert result.exit_code == 0
|
|
73
|
+
assert "Found 1 service(s)" in result.output
|
|
74
|
+
assert "Dry run mode" in result.output
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_import_services_cli_full(plugin, runner):
|
|
78
|
+
"""Test full import_services CLI with confirmation."""
|
|
79
|
+
app = typer.Typer()
|
|
80
|
+
app.command()(plugin.import_services)
|
|
81
|
+
|
|
82
|
+
with patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls:
|
|
83
|
+
mock_importer = mock_importer_cls.return_value
|
|
84
|
+
# Mock discovered service with an encrypted key to trigger password prompt
|
|
85
|
+
key = DiscoveredKey(address="0x1", is_encrypted=True, role="agent")
|
|
86
|
+
service = DiscoveredService(
|
|
87
|
+
service_id=1, service_name="Test", chain_name="gnosis", keys=[key]
|
|
88
|
+
)
|
|
89
|
+
mock_importer.scan_directory.return_value = [service]
|
|
90
|
+
|
|
91
|
+
# Mock successful import
|
|
92
|
+
mock_importer.import_service.return_value = ImportResult(
|
|
93
|
+
success=True, message="Imported", imported_services=["gnosis:1"]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Test with -y and providing password
|
|
97
|
+
result = runner.invoke(app, ["/tmp/test", "-y", "-p", "secret"])
|
|
98
|
+
assert result.exit_code == 0
|
|
99
|
+
assert "Imported services: gnosis:1" in result.output
|
|
100
|
+
assert "Summary" in result.output
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_get_safe_signers_edge_cases(plugin):
|
|
104
|
+
"""Test _get_safe_signers with various failure scenarios."""
|
|
105
|
+
# 1. No RPC configured
|
|
106
|
+
with patch("iwa.core.settings.settings") as mock_settings:
|
|
107
|
+
mock_settings.gnosis_rpc = None
|
|
108
|
+
signers, exists = plugin._get_safe_signers("0x1", "gnosis")
|
|
109
|
+
assert signers is None
|
|
110
|
+
assert exists is None
|
|
111
|
+
|
|
112
|
+
# 2. Safe doesn't exist (raises exception)
|
|
113
|
+
with patch("iwa.core.settings.settings") as mock_settings:
|
|
114
|
+
mock_settings.gnosis_rpc = MagicMock()
|
|
115
|
+
with patch("safe_eth.eth.EthereumClient"), patch("safe_eth.safe.Safe") as mock_safe_cls:
|
|
116
|
+
mock_safe = mock_safe_cls.return_value
|
|
117
|
+
mock_safe.retrieve_owners.side_effect = Exception("Generic error")
|
|
118
|
+
|
|
119
|
+
signers, exists = plugin._get_safe_signers("0x1", "gnosis")
|
|
120
|
+
assert signers == []
|
|
121
|
+
assert exists is False
|
|
122
|
+
|
|
123
|
+
# 3. Success path
|
|
124
|
+
with patch("iwa.core.settings.settings") as mock_settings:
|
|
125
|
+
mock_settings.gnosis_rpc = MagicMock()
|
|
126
|
+
with patch("safe_eth.eth.EthereumClient"), patch("safe_eth.safe.Safe") as mock_safe_cls:
|
|
127
|
+
mock_safe = mock_safe_cls.return_value
|
|
128
|
+
mock_safe.retrieve_owners.return_value = ["0xAgent"]
|
|
129
|
+
|
|
130
|
+
signers, exists = plugin._get_safe_signers("0x1", "gnosis")
|
|
131
|
+
assert signers == ["0xAgent"]
|
|
132
|
+
assert exists is True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_import_services_cli_abort(plugin, runner):
|
|
136
|
+
"""Test import_services CLI aborting on confirmation."""
|
|
137
|
+
app = typer.Typer()
|
|
138
|
+
app.command()(plugin.import_services)
|
|
139
|
+
|
|
140
|
+
with patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls:
|
|
141
|
+
mock_importer = mock_importer_cls.return_value
|
|
142
|
+
mock_importer.scan_directory.return_value = [
|
|
143
|
+
DiscoveredService(service_id=1, service_name="Test", chain_name="gnosis")
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
# Simulate 'n' to confirmation prompt
|
|
147
|
+
result = runner.invoke(app, ["/tmp/test"], input="n\n")
|
|
148
|
+
assert result.exit_code == 0
|
|
149
|
+
assert "Aborted" in result.output
|
|
150
|
+
mock_importer.import_service.assert_not_called()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_import_services_cli_no_services(plugin, runner):
|
|
154
|
+
"""Test import_services CLI when no services are found."""
|
|
155
|
+
app = typer.Typer()
|
|
156
|
+
app.command()(plugin.import_services)
|
|
157
|
+
|
|
158
|
+
with patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls:
|
|
159
|
+
mock_importer = mock_importer_cls.return_value
|
|
160
|
+
mock_importer.scan_directory.return_value = []
|
|
161
|
+
|
|
162
|
+
result = runner.invoke(app, ["/tmp/test"])
|
|
163
|
+
assert result.exit_code == 0
|
|
164
|
+
assert "No Olas services found" in result.output
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_import_services_cli_complex_display(plugin, runner):
|
|
168
|
+
"""Test import_services CLI display logic with Safe verification and signer check."""
|
|
169
|
+
app = typer.Typer()
|
|
170
|
+
app.command()(plugin.import_services)
|
|
171
|
+
|
|
172
|
+
with (
|
|
173
|
+
patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls,
|
|
174
|
+
patch.object(OlasPlugin, "_get_safe_signers") as mock_get_signers,
|
|
175
|
+
):
|
|
176
|
+
mock_importer = mock_importer_cls.return_value
|
|
177
|
+
# 1. Service with valid Safe and agent is signer
|
|
178
|
+
key = DiscoveredKey(address="0xAgent", role="agent")
|
|
179
|
+
service = DiscoveredService(service_id=1, safe_address="0xSafe", keys=[key])
|
|
180
|
+
mock_importer.scan_directory.return_value = [service]
|
|
181
|
+
|
|
182
|
+
# Mock Safe exists with Agent as signer
|
|
183
|
+
mock_get_signers.return_value = (["0xAgent"], True)
|
|
184
|
+
|
|
185
|
+
result = runner.invoke(app, ["/tmp/test", "--dry-run"])
|
|
186
|
+
assert "0xSafe" in result.output
|
|
187
|
+
assert "✓" in result.output
|
|
188
|
+
assert "0xAgent 🔓 plaintext" in result.output # Not a warning
|
|
189
|
+
|
|
190
|
+
# 2. Service where agent is NOT a signer
|
|
191
|
+
mock_get_signers.return_value = (["0xOther"], True)
|
|
192
|
+
result = runner.invoke(app, ["/tmp/test", "--dry-run"])
|
|
193
|
+
assert "NOT A SIGNER OF THE SAFE" in result.output
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_import_services_cli_password_prompt(plugin, runner):
|
|
197
|
+
"""Test import_services CLI prompting for password."""
|
|
198
|
+
app = typer.Typer()
|
|
199
|
+
app.command()(plugin.import_services)
|
|
200
|
+
|
|
201
|
+
with patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls:
|
|
202
|
+
mock_importer = mock_importer_cls.return_value
|
|
203
|
+
key = DiscoveredKey(address="0x1", is_encrypted=True, role="agent")
|
|
204
|
+
service = DiscoveredService(service_id=1, keys=[key])
|
|
205
|
+
mock_importer.scan_directory.return_value = [service]
|
|
206
|
+
mock_importer.import_service.return_value = ImportResult(success=True, message="OK")
|
|
207
|
+
|
|
208
|
+
# input="y\nsecret\n" -> 'y' for confirm import, 'secret' for password prompt
|
|
209
|
+
result = runner.invoke(app, ["/tmp/test"], input="y\nsecret\n")
|
|
210
|
+
assert result.exit_code == 0
|
|
211
|
+
assert "password" in result.output.lower()
|
|
212
|
+
mock_importer.import_service.assert_called_with(service, "secret")
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Tests for Olas service lifecycle: create, activate, register, deploy."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from iwa.plugins.olas.contracts.service import ServiceState
|
|
8
|
+
from iwa.plugins.olas.importer import DiscoveredKey, DiscoveredService, OlasServiceImporter
|
|
9
|
+
from iwa.plugins.olas.models import OlasConfig, Service
|
|
10
|
+
from iwa.plugins.olas.service_manager import ServiceManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def mock_wallet():
|
|
15
|
+
"""Mock wallet with master account."""
|
|
16
|
+
w = MagicMock()
|
|
17
|
+
w.master_account.address = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
|
|
18
|
+
return w
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def sm(mock_wallet):
|
|
23
|
+
"""ServiceManager fixture."""
|
|
24
|
+
with (
|
|
25
|
+
patch("iwa.core.models.Config"),
|
|
26
|
+
patch("iwa.core.contracts.contract.ChainInterfaces") as mock_ci,
|
|
27
|
+
):
|
|
28
|
+
mock_ci.get_instance.return_value.get.return_value.chain.get_token_address.side_effect = (
|
|
29
|
+
lambda x: x
|
|
30
|
+
)
|
|
31
|
+
return ServiceManager(mock_wallet)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def importer(mock_wallet):
|
|
36
|
+
"""OlasServiceImporter fixture."""
|
|
37
|
+
with patch("iwa.core.models.Config"):
|
|
38
|
+
return OlasServiceImporter(mock_wallet)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# --- ServiceManager Edge Cases ---
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_sm_create_utility_not_found(sm):
|
|
45
|
+
"""Target service_manager.py:203 (utility not found)."""
|
|
46
|
+
with patch("iwa.plugins.olas.constants.OLAS_CONTRACTS", {"gnosis": {}}):
|
|
47
|
+
sm.wallet.sign_and_send_transaction.return_value = (True, {"status": 1})
|
|
48
|
+
# Mocking registry.extract_events which is what sm.create now uses
|
|
49
|
+
sm.registry.extract_events = MagicMock(
|
|
50
|
+
return_value=[{"name": "CreateService", "args": {"serviceId": 42}}]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
sid = sm.create("gnosis", "test")
|
|
54
|
+
assert sid == 42
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_sm_create_approve_fail(sm):
|
|
58
|
+
"""Target service_manager.py:217 (approve fail)."""
|
|
59
|
+
sm.wallet.sign_and_send_transaction.return_value = (True, {"status": 1})
|
|
60
|
+
sm.registry.extract_events = MagicMock(
|
|
61
|
+
return_value=[{"name": "CreateService", "args": {"serviceId": 42}}]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
sm.transfer_service.approve_erc20.return_value = False
|
|
65
|
+
sid = sm.create(
|
|
66
|
+
"gnosis", "test", token_address_or_tag="0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
|
|
67
|
+
)
|
|
68
|
+
assert sid == 42
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_sm_activate_not_preregistration(sm):
|
|
72
|
+
"""Target service_manager.py:228 (state mismatch)."""
|
|
73
|
+
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
74
|
+
sm.registry = MagicMock()
|
|
75
|
+
sm.registry.get_service.return_value = {"state": ServiceState.DEPLOYED}
|
|
76
|
+
success = sm.activate_registration()
|
|
77
|
+
assert success is False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_sm_checkpoint_check_exception(sm):
|
|
81
|
+
"""Target service_manager.py:597 (checkpoint check exception)."""
|
|
82
|
+
with patch("iwa.plugins.olas.contracts.staking.StakingContract") as mock_stk_cls:
|
|
83
|
+
mock_stk = mock_stk_cls.return_value
|
|
84
|
+
mock_stk.is_checkpoint_needed.side_effect = Exception("error")
|
|
85
|
+
success = sm.call_checkpoint("gnosis")
|
|
86
|
+
assert success is False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_sm_stake_fail(sm):
|
|
90
|
+
"""Target service_manager.py:690 (stake fail)."""
|
|
91
|
+
addr = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
|
|
92
|
+
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
93
|
+
sm.registry = MagicMock()
|
|
94
|
+
sm.registry.get_service.return_value = {"state": ServiceState.DEPLOYED, "security_deposit": 1}
|
|
95
|
+
sm.transfer_service.approve_erc20.return_value = True
|
|
96
|
+
sm.wallet.sign_and_send_transaction.return_value = (False, None)
|
|
97
|
+
|
|
98
|
+
with (
|
|
99
|
+
patch("iwa.plugins.olas.contracts.staking.StakingContract") as mock_stk_cls,
|
|
100
|
+
patch("iwa.plugins.olas.service_manager.staking.ERC20Contract"),
|
|
101
|
+
):
|
|
102
|
+
mock_stk = mock_stk_cls.return_value
|
|
103
|
+
mock_stk.get_service_info.return_value = {"staking_state": 1}
|
|
104
|
+
mock_stk.staking_token_address = addr
|
|
105
|
+
mock_stk.get_requirements.return_value = {
|
|
106
|
+
"staking_token": addr,
|
|
107
|
+
"min_staking_deposit": 50000000000000000000,
|
|
108
|
+
"num_agent_instances": 1,
|
|
109
|
+
"required_agent_bond": 50000000000000000000,
|
|
110
|
+
}
|
|
111
|
+
success = sm.stake(mock_stk)
|
|
112
|
+
assert success is False
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# --- Importer Edge Cases ---
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_importer_encrypted_no_pwd(importer):
|
|
119
|
+
"""Target importer.py:192 (encrypted key without password)."""
|
|
120
|
+
addr = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
|
|
121
|
+
key = DiscoveredKey(address=addr, is_encrypted=True, encrypted_keystore={"crypto": {}})
|
|
122
|
+
importer.key_storage.find_stored_account.return_value = None
|
|
123
|
+
success, msg = importer._import_key(key, "service", password=None)
|
|
124
|
+
assert success is False
|
|
125
|
+
assert "password" in msg
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_importer_safe_duplicate(importer):
|
|
129
|
+
"""Target importer.py:308 (duplicate safe)."""
|
|
130
|
+
addr = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
|
|
131
|
+
service = DiscoveredService(service_id=1, chain_name="gnosis", safe_address=addr)
|
|
132
|
+
importer.key_storage.find_stored_account.return_value = MagicMock()
|
|
133
|
+
success, msg = importer._import_safe(service)
|
|
134
|
+
assert success is False
|
|
135
|
+
assert msg == "duplicate"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_olas_config_remove_not_exists():
|
|
139
|
+
"""Target models.py:85-88 (remove service not exists)."""
|
|
140
|
+
config = OlasConfig()
|
|
141
|
+
assert config.remove_service("not:exists") is False
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_olas_config_get_service_by_multisig():
|
|
145
|
+
"""Target models.py:106-110 (get by multisig)."""
|
|
146
|
+
addr = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
|
|
147
|
+
s = Service(service_name="t", chain_name="g", service_id=1, multisig_address=addr)
|
|
148
|
+
config = OlasConfig(services={"g:1": s})
|
|
149
|
+
assert config.get_service_by_multisig(addr) == s
|
|
150
|
+
assert config.get_service_by_multisig("0x0000000000000000000000000000000000000000") is None
|