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,430 @@
|
|
|
1
|
+
"""Tests for Olas Web API endpoints."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from fastapi.testclient import TestClient
|
|
7
|
+
|
|
8
|
+
# We need to mock Wallet and ChainInterfaces BEFORE importing app from server
|
|
9
|
+
# Also mock _get_webui_password to bypass authentication in tests
|
|
10
|
+
with (
|
|
11
|
+
patch("iwa.core.wallet.Wallet"),
|
|
12
|
+
patch("iwa.core.chain.ChainInterfaces"),
|
|
13
|
+
patch("iwa.core.wallet.init_db"),
|
|
14
|
+
patch("iwa.web.dependencies._get_webui_password", return_value=None),
|
|
15
|
+
):
|
|
16
|
+
from iwa.web.dependencies import verify_auth
|
|
17
|
+
from iwa.web.server import app
|
|
18
|
+
|
|
19
|
+
from iwa.plugins.olas.models import OlasConfig, Service, StakingStatus
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Override auth for all tests
|
|
23
|
+
async def override_verify_auth():
|
|
24
|
+
"""Override auth for testing."""
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
app.dependency_overrides[verify_auth] = override_verify_auth
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def client():
|
|
33
|
+
"""TestClient for FastAPI app."""
|
|
34
|
+
return TestClient(app)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def mock_olas_config():
|
|
39
|
+
"""Mock Olas configuration."""
|
|
40
|
+
service = Service(
|
|
41
|
+
service_id=1,
|
|
42
|
+
service_name="Test Service",
|
|
43
|
+
chain_name="gnosis",
|
|
44
|
+
agent_address="0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB",
|
|
45
|
+
multisig_address="0x40A2aCCbd92BCA938b02010E17A5b8929b49130D",
|
|
46
|
+
service_owner_address="0x1111111111111111111111111111111111111111",
|
|
47
|
+
staking_contract_address="0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB",
|
|
48
|
+
)
|
|
49
|
+
return OlasConfig(services={"gnosis:1": service})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_get_olas_price(client):
|
|
53
|
+
"""Test /api/olas/price endpoint."""
|
|
54
|
+
with patch("iwa.core.pricing.PriceService") as mock_price_cls:
|
|
55
|
+
mock_price_cls.return_value.get_token_price.return_value = 5.0
|
|
56
|
+
response = client.get("/api/olas/price")
|
|
57
|
+
assert response.status_code == 200
|
|
58
|
+
assert response.json() == {"price_eur": 5.0, "symbol": "OLAS"}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_get_olas_services_basic(client, mock_olas_config):
|
|
62
|
+
"""Test /api/olas/services/basic endpoint."""
|
|
63
|
+
with patch("iwa.web.routers.olas.services.Config") as mock_config_cls:
|
|
64
|
+
mock_config = mock_config_cls.return_value
|
|
65
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
66
|
+
|
|
67
|
+
# Mock wallet.key_storage.find_stored_account
|
|
68
|
+
from iwa.web.dependencies import wallet
|
|
69
|
+
|
|
70
|
+
wallet.key_storage.find_stored_account.return_value = MagicMock(tag="test_tag")
|
|
71
|
+
|
|
72
|
+
response = client.get("/api/olas/services/basic?chain=gnosis")
|
|
73
|
+
assert response.status_code == 200
|
|
74
|
+
data = response.json()
|
|
75
|
+
assert len(data) == 1
|
|
76
|
+
assert data[0]["name"] == "Test Service"
|
|
77
|
+
assert data[0]["accounts"]["agent"]["tag"] == "test_tag"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_get_olas_service_details(client, mock_olas_config):
|
|
81
|
+
"""Test /api/olas/services/{service_key}/details endpoint."""
|
|
82
|
+
with (
|
|
83
|
+
patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
|
|
84
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
85
|
+
):
|
|
86
|
+
mock_config = mock_config_cls.return_value
|
|
87
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
88
|
+
|
|
89
|
+
mock_sm = mock_sm_cls.return_value
|
|
90
|
+
mock_sm.get_staking_status.return_value = StakingStatus(
|
|
91
|
+
is_staked=True,
|
|
92
|
+
staking_state="STAKED",
|
|
93
|
+
accrued_reward_olas=10.5,
|
|
94
|
+
remaining_epoch_seconds=3600,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
from iwa.web.dependencies import wallet
|
|
98
|
+
|
|
99
|
+
wallet.get_native_balance_eth.return_value = 1.0
|
|
100
|
+
wallet.balance_service.get_erc20_balance_wei.return_value = 10**18
|
|
101
|
+
wallet.key_storage.find_stored_account.return_value = MagicMock(tag="test_tag")
|
|
102
|
+
|
|
103
|
+
response = client.get("/api/olas/services/gnosis:1/details")
|
|
104
|
+
assert response.status_code == 200
|
|
105
|
+
data = response.json()
|
|
106
|
+
assert data["staking"]["is_staked"] is True
|
|
107
|
+
assert data["staking"]["accrued_reward_olas"] == 10.5
|
|
108
|
+
assert data["accounts"]["agent"]["native"] == "1.00"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_get_olas_services_full(client, mock_olas_config):
|
|
112
|
+
"""Test /api/olas/services (full) endpoint."""
|
|
113
|
+
with (
|
|
114
|
+
patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
|
|
115
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
116
|
+
):
|
|
117
|
+
mock_config = mock_config_cls.return_value
|
|
118
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
119
|
+
|
|
120
|
+
mock_sm = mock_sm_cls.return_value
|
|
121
|
+
mock_sm.get_staking_status.return_value = StakingStatus(
|
|
122
|
+
is_staked=True, staking_state="STAKED"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
from iwa.web.dependencies import wallet
|
|
126
|
+
|
|
127
|
+
wallet.get_native_balance_eth.return_value = 1.0
|
|
128
|
+
wallet.balance_service.get_erc20_balance_wei.return_value = 10**18
|
|
129
|
+
|
|
130
|
+
response = client.get("/api/olas/services?chain=gnosis")
|
|
131
|
+
assert response.status_code == 200
|
|
132
|
+
data = response.json()
|
|
133
|
+
assert len(data) == 1
|
|
134
|
+
assert data[0]["staking"]["is_staked"] is True
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_olas_actions(client, mock_olas_config):
|
|
138
|
+
"""Test Olas action endpoints (claim, unstake, checkpoint)."""
|
|
139
|
+
with (
|
|
140
|
+
patch("iwa.web.routers.olas.staking.Config") as mock_config_cls,
|
|
141
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
142
|
+
):
|
|
143
|
+
mock_config = mock_config_cls.return_value
|
|
144
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
145
|
+
|
|
146
|
+
mock_sm = mock_sm_cls.return_value
|
|
147
|
+
mock_sm.claim_rewards.return_value = (True, 10**18)
|
|
148
|
+
mock_sm.unstake.return_value = True
|
|
149
|
+
mock_sm.call_checkpoint.return_value = True
|
|
150
|
+
|
|
151
|
+
# Mock StakingContract.from_address and ChainInterfaces
|
|
152
|
+
with patch("iwa.plugins.olas.contracts.staking.StakingContract"):
|
|
153
|
+
from iwa.core.chain import ChainInterfaces
|
|
154
|
+
|
|
155
|
+
# Properly access the return value of the mocked class singleton-like usage
|
|
156
|
+
if hasattr(ChainInterfaces, "return_value"):
|
|
157
|
+
ChainInterfaces.return_value.get.return_value.chain = MagicMock()
|
|
158
|
+
|
|
159
|
+
# mock_sc = mock_sc_cls.from_address.return_value
|
|
160
|
+
|
|
161
|
+
# Claim
|
|
162
|
+
response = client.post("/api/olas/claim/gnosis:1")
|
|
163
|
+
assert response.status_code == 200
|
|
164
|
+
assert response.json()["status"] == "success"
|
|
165
|
+
|
|
166
|
+
# Unstake
|
|
167
|
+
response = client.post("/api/olas/unstake/gnosis:1")
|
|
168
|
+
assert response.status_code == 200
|
|
169
|
+
assert response.json()["status"] == "success"
|
|
170
|
+
|
|
171
|
+
# Checkpoint
|
|
172
|
+
response = client.post("/api/olas/checkpoint/gnosis:1")
|
|
173
|
+
assert response.status_code == 200
|
|
174
|
+
assert response.json()["status"] == "success"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# --- Additional tests for uncovered endpoints ---
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_get_staking_contracts(client):
|
|
181
|
+
"""Test /api/olas/staking-contracts endpoint - returns response with contracts and filter_info."""
|
|
182
|
+
response = client.get("/api/olas/staking-contracts?chain=gnosis")
|
|
183
|
+
assert response.status_code == 200
|
|
184
|
+
data = response.json()
|
|
185
|
+
# New format: {contracts: [...], filter_info: {...}}
|
|
186
|
+
assert isinstance(data, dict)
|
|
187
|
+
assert "contracts" in data
|
|
188
|
+
assert "filter_info" in data
|
|
189
|
+
assert isinstance(data["contracts"], list)
|
|
190
|
+
assert isinstance(data["filter_info"], dict)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_create_service(client, mock_olas_config):
|
|
194
|
+
"""Test /api/olas/create endpoint."""
|
|
195
|
+
with (
|
|
196
|
+
patch(
|
|
197
|
+
"iwa.web.routers.olas.services.Config"
|
|
198
|
+
) as mock_config_cls, # Not strictly used but kept for consistency if needed
|
|
199
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
200
|
+
):
|
|
201
|
+
mock_config = mock_config_cls.return_value
|
|
202
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
203
|
+
|
|
204
|
+
mock_sm = mock_sm_cls.return_value
|
|
205
|
+
mock_sm.create.return_value = 123
|
|
206
|
+
mock_sm.spin_up.return_value = True
|
|
207
|
+
mock_sm.service = MagicMock()
|
|
208
|
+
mock_sm.service.service_id = 123
|
|
209
|
+
|
|
210
|
+
response = client.post(
|
|
211
|
+
"/api/olas/create",
|
|
212
|
+
json={
|
|
213
|
+
"service_name": "Test Service",
|
|
214
|
+
"chain": "gnosis",
|
|
215
|
+
"bond_amount_olas": 100,
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
assert response.status_code == 200
|
|
219
|
+
data = response.json()
|
|
220
|
+
assert data["status"] == "success"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_create_service_failure(client, mock_olas_config):
|
|
224
|
+
"""Test /api/olas/create when creation fails."""
|
|
225
|
+
with (
|
|
226
|
+
patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
|
|
227
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
228
|
+
):
|
|
229
|
+
mock_config = mock_config_cls.return_value
|
|
230
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
231
|
+
|
|
232
|
+
mock_sm = mock_sm_cls.return_value
|
|
233
|
+
mock_sm.create.return_value = None # Creation fails
|
|
234
|
+
|
|
235
|
+
response = client.post(
|
|
236
|
+
"/api/olas/create",
|
|
237
|
+
json={
|
|
238
|
+
"service_name": "Test Service",
|
|
239
|
+
"chain": "gnosis",
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
# API returns 400 on creation failure
|
|
243
|
+
assert response.status_code == 400
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def test_deploy_service(client, mock_olas_config):
|
|
247
|
+
"""Test /api/olas/deploy/{service_key} endpoint."""
|
|
248
|
+
with (
|
|
249
|
+
patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
|
|
250
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
251
|
+
):
|
|
252
|
+
mock_config = mock_config_cls.return_value
|
|
253
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
254
|
+
|
|
255
|
+
mock_sm = mock_sm_cls.return_value
|
|
256
|
+
mock_sm.spin_up.return_value = True
|
|
257
|
+
mock_sm.service = mock_olas_config.services["gnosis:1"]
|
|
258
|
+
mock_sm.get_service_state.return_value = "PRE_REGISTRATION"
|
|
259
|
+
|
|
260
|
+
response = client.post("/api/olas/deploy/gnosis:1")
|
|
261
|
+
assert response.status_code == 200
|
|
262
|
+
assert response.json()["status"] == "success"
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def test_deploy_service_failure(client, mock_olas_config):
|
|
266
|
+
"""Test /api/olas/deploy/{service_key} when deployment fails."""
|
|
267
|
+
with (
|
|
268
|
+
patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
|
|
269
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
270
|
+
):
|
|
271
|
+
mock_config = mock_config_cls.return_value
|
|
272
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
273
|
+
|
|
274
|
+
mock_sm = mock_sm_cls.return_value
|
|
275
|
+
mock_sm.spin_up.return_value = False
|
|
276
|
+
mock_sm.get_service_state.return_value = "PRE_REGISTRATION"
|
|
277
|
+
|
|
278
|
+
response = client.post("/api/olas/deploy/gnosis:1")
|
|
279
|
+
# API returns 400 on spin_up failure
|
|
280
|
+
assert response.status_code == 400
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def test_activate_registration(client, mock_olas_config):
|
|
284
|
+
"""Test /api/olas/activate/{service_key} endpoint."""
|
|
285
|
+
with (
|
|
286
|
+
patch("iwa.web.routers.olas.admin.Config") as mock_config_cls,
|
|
287
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
288
|
+
):
|
|
289
|
+
mock_config = mock_config_cls.return_value
|
|
290
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
291
|
+
|
|
292
|
+
mock_sm = mock_sm_cls.return_value
|
|
293
|
+
mock_sm.activate_registration.return_value = True
|
|
294
|
+
|
|
295
|
+
response = client.post("/api/olas/activate/gnosis:1")
|
|
296
|
+
assert response.status_code == 200
|
|
297
|
+
assert response.json()["status"] == "success"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_register_agent(client, mock_olas_config):
|
|
301
|
+
"""Test /api/olas/register/{service_key} endpoint."""
|
|
302
|
+
with (
|
|
303
|
+
patch("iwa.web.routers.olas.admin.Config") as mock_config_cls,
|
|
304
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
305
|
+
):
|
|
306
|
+
mock_config = mock_config_cls.return_value
|
|
307
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
308
|
+
|
|
309
|
+
mock_sm = mock_sm_cls.return_value
|
|
310
|
+
mock_sm.register_agent.return_value = True
|
|
311
|
+
|
|
312
|
+
# Correct URL is /register/ not /register-agent/
|
|
313
|
+
response = client.post("/api/olas/register/gnosis:1")
|
|
314
|
+
assert response.status_code == 200
|
|
315
|
+
assert response.json()["status"] == "success"
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def test_deploy_step(client, mock_olas_config):
|
|
319
|
+
"""Test /api/olas/deploy-step/{service_key} endpoint."""
|
|
320
|
+
with (
|
|
321
|
+
patch("iwa.web.routers.olas.admin.Config") as mock_config_cls,
|
|
322
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
323
|
+
):
|
|
324
|
+
mock_config = mock_config_cls.return_value
|
|
325
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
326
|
+
|
|
327
|
+
mock_sm = mock_sm_cls.return_value
|
|
328
|
+
mock_sm.deploy.return_value = "0xMultisig123" # Returns multisig address
|
|
329
|
+
|
|
330
|
+
response = client.post("/api/olas/deploy-step/gnosis:1")
|
|
331
|
+
assert response.status_code == 200
|
|
332
|
+
data = response.json()
|
|
333
|
+
# API only returns {"status": "success"}, not multisig
|
|
334
|
+
assert data["status"] == "success"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def test_stake_service(client, mock_olas_config):
|
|
338
|
+
"""Test /api/olas/stake/{service_key} endpoint."""
|
|
339
|
+
with (
|
|
340
|
+
patch("iwa.web.routers.olas.staking.Config") as mock_config_cls,
|
|
341
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
342
|
+
patch("iwa.plugins.olas.contracts.staking.StakingContract"),
|
|
343
|
+
):
|
|
344
|
+
mock_config = mock_config_cls.return_value
|
|
345
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
346
|
+
|
|
347
|
+
mock_sm = mock_sm_cls.return_value
|
|
348
|
+
mock_sm.stake.return_value = True
|
|
349
|
+
|
|
350
|
+
response = client.post(
|
|
351
|
+
"/api/olas/stake/gnosis:1?staking_contract=0x1234567890123456789012345678901234567890"
|
|
352
|
+
)
|
|
353
|
+
assert response.status_code == 200
|
|
354
|
+
assert response.json()["status"] == "success"
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def test_terminate_service(client, mock_olas_config):
|
|
358
|
+
"""Test /api/olas/terminate/{service_key} endpoint."""
|
|
359
|
+
with (
|
|
360
|
+
patch("iwa.web.routers.olas.admin.Config") as mock_config_cls,
|
|
361
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
362
|
+
patch("iwa.plugins.olas.contracts.staking.StakingContract"),
|
|
363
|
+
):
|
|
364
|
+
mock_config = mock_config_cls.return_value
|
|
365
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
366
|
+
|
|
367
|
+
mock_sm = mock_sm_cls.return_value
|
|
368
|
+
mock_sm.wind_down.return_value = True
|
|
369
|
+
|
|
370
|
+
response = client.post("/api/olas/terminate/gnosis:1")
|
|
371
|
+
assert response.status_code == 200
|
|
372
|
+
assert response.json()["status"] == "success"
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def test_fund_service(client, mock_olas_config):
|
|
376
|
+
"""Test /api/olas/fund/{service_key} endpoint."""
|
|
377
|
+
with (
|
|
378
|
+
patch("iwa.web.routers.olas.funding.Config") as mock_config_cls,
|
|
379
|
+
patch("iwa.web.routers.olas.funding.wallet") as mock_wallet,
|
|
380
|
+
):
|
|
381
|
+
mock_config = mock_config_cls.return_value
|
|
382
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
383
|
+
|
|
384
|
+
mock_wallet.send.return_value = "0xTxHash"
|
|
385
|
+
|
|
386
|
+
response = client.post(
|
|
387
|
+
"/api/olas/fund/gnosis:1",
|
|
388
|
+
json={"agent_amount_eth": 1.0, "safe_amount_eth": 2.0},
|
|
389
|
+
)
|
|
390
|
+
assert response.status_code == 200
|
|
391
|
+
data = response.json()
|
|
392
|
+
assert data["status"] == "success"
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def test_drain_service(client, mock_olas_config):
|
|
396
|
+
"""Test /api/olas/drain/{service_key} endpoint."""
|
|
397
|
+
with (
|
|
398
|
+
patch("iwa.web.routers.olas.funding.Config") as mock_config_cls,
|
|
399
|
+
patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
|
|
400
|
+
):
|
|
401
|
+
mock_config = mock_config_cls.return_value
|
|
402
|
+
mock_config.plugins = {"olas": mock_olas_config.model_dump()}
|
|
403
|
+
|
|
404
|
+
mock_sm = mock_sm_cls.return_value
|
|
405
|
+
mock_sm.drain_service.return_value = {
|
|
406
|
+
"safe": {"native": 1.5, "olas": 100.0},
|
|
407
|
+
"agent": {"native": 0.5},
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
response = client.post("/api/olas/drain/gnosis:1")
|
|
411
|
+
assert response.status_code == 200
|
|
412
|
+
data = response.json()
|
|
413
|
+
assert data["status"] == "success"
|
|
414
|
+
assert "safe" in data["drained"]
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def test_service_not_found(client):
|
|
418
|
+
"""Test endpoints return 404 when service not found."""
|
|
419
|
+
with patch("iwa.web.routers.olas.admin.Config") as mock_config_cls:
|
|
420
|
+
mock_config = mock_config_cls.return_value
|
|
421
|
+
mock_config.plugins = {"olas": OlasConfig().model_dump()}
|
|
422
|
+
|
|
423
|
+
response = client.post("/api/olas/activate/gnosis:999")
|
|
424
|
+
assert response.status_code == 404
|
|
425
|
+
|
|
426
|
+
response = client.post("/api/olas/register/gnosis:999")
|
|
427
|
+
assert response.status_code == 404
|
|
428
|
+
|
|
429
|
+
response = client.post("/api/olas/deploy-step/gnosis:999")
|
|
430
|
+
assert response.status_code == 404
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Tests for Swap Web API endpoints."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from fastapi.testclient import TestClient
|
|
7
|
+
|
|
8
|
+
# We need to mock Wallet and ChainInterfaces BEFORE importing app from server
|
|
9
|
+
# Also mock _get_webui_password to bypass authentication in tests
|
|
10
|
+
with (
|
|
11
|
+
patch("iwa.core.wallet.Wallet"),
|
|
12
|
+
patch("iwa.core.chain.ChainInterfaces"),
|
|
13
|
+
patch("iwa.core.wallet.init_db"),
|
|
14
|
+
patch("iwa.web.dependencies._get_webui_password", return_value=None),
|
|
15
|
+
):
|
|
16
|
+
from iwa.web.dependencies import verify_auth
|
|
17
|
+
from iwa.web.server import app
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Override auth for all tests
|
|
21
|
+
async def override_verify_auth():
|
|
22
|
+
"""Override auth for testing."""
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
app.dependency_overrides[verify_auth] = override_verify_auth
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def client():
|
|
31
|
+
"""TestClient for FastAPI app."""
|
|
32
|
+
return TestClient(app)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_get_swap_quote_invalid_account(client):
|
|
36
|
+
"""Test /api/swap/quote with invalid account."""
|
|
37
|
+
with patch("iwa.web.routers.swap.wallet") as mock_wallet:
|
|
38
|
+
mock_wallet.account_service.resolve_account.return_value = None
|
|
39
|
+
|
|
40
|
+
response = client.get(
|
|
41
|
+
"/api/swap/quote?account=invalid&sell_token=WXDAI&buy_token=OLAS&amount=1.0"
|
|
42
|
+
)
|
|
43
|
+
assert response.status_code == 400
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_swap_tokens_invalid_order_type(client):
|
|
47
|
+
"""Test /api/swap with invalid order type."""
|
|
48
|
+
response = client.post(
|
|
49
|
+
"/api/swap",
|
|
50
|
+
json={
|
|
51
|
+
"account": "master",
|
|
52
|
+
"sell_token": "WXDAI",
|
|
53
|
+
"buy_token": "OLAS",
|
|
54
|
+
"amount": 1.0,
|
|
55
|
+
"order_type": "invalid",
|
|
56
|
+
"chain": "gnosis",
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
# Should return 422 for validation error
|
|
60
|
+
assert response.status_code == 422
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_swap_quote_invalid_mode(client):
|
|
64
|
+
"""Test /api/swap/quote with invalid mode."""
|
|
65
|
+
response = client.get(
|
|
66
|
+
"/api/swap/quote?account=master&sell_token=WXDAI&buy_token=OLAS&amount=1.0&mode=invalid"
|
|
67
|
+
)
|
|
68
|
+
assert response.status_code == 400
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_swap_tokens_negative_amount(client):
|
|
72
|
+
"""Test /api/swap with negative amount."""
|
|
73
|
+
response = client.post(
|
|
74
|
+
"/api/swap",
|
|
75
|
+
json={
|
|
76
|
+
"account": "master",
|
|
77
|
+
"sell_token": "WXDAI",
|
|
78
|
+
"buy_token": "OLAS",
|
|
79
|
+
"amount": -1.0,
|
|
80
|
+
"order_type": "sell",
|
|
81
|
+
"chain": "gnosis",
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
# Should return 422 for validation error, but sometimes 400 depending on handler
|
|
85
|
+
# Accepted behavior is rejection.
|
|
86
|
+
assert response.status_code in (400, 422)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_swap_tokens_invalid_chain(client):
|
|
90
|
+
"""Test /api/swap with invalid chain."""
|
|
91
|
+
response = client.post(
|
|
92
|
+
"/api/swap",
|
|
93
|
+
json={
|
|
94
|
+
"account": "master",
|
|
95
|
+
"sell_token": "WXDAI",
|
|
96
|
+
"buy_token": "OLAS",
|
|
97
|
+
"amount": 1.0,
|
|
98
|
+
"order_type": "sell",
|
|
99
|
+
"chain": "invalid!chain",
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
# Should return 422 for validation error
|
|
103
|
+
assert response.status_code == 422
|